ASP.NET Custom Control, Two Grids.. Need some advice - javascript

I need some advice designing a custom control which uses two grids and an Add and Remove button in between.
The Add button takes the selected item from left and add it to the right and then it removes it from the left.
The Remove button does the vice versa.
To have a fluid experience, I understand that Javascript will probably have to be involved.
Currently I'm creating a control inheriting CompositeControl with two grids and two sources. I could use a UpdatePanel so I don't have to do do a full post back on Add/Remove.
Any suggestions on the best way to approach this?

I did this sample using Kendo. I write some parts .I hope it would be helpful
I add and remove some paths to supervisors in my sample:
you need a main Action like this:
public ActionResult AddPathToSupervisor()
{
return View();
}
My sample is a bit more complete because in the view at first you choose a superviser and after that you add some paths to him.
within the view you need 2 Grids and 2 buttons between for Add/Remove
like this:
<div class="row">
<div class="col large">
#(Html.Kendo().ComboBox()
.Name("supervisor")
.BindTo(SupervisorsSelectList)//var DocumetTypesSelectList = ViewBag.DocumetTypesSelectList as List<SelectListItem> ?? new List<SelectListItem>();
.Events(e => e.Change("changeSupervisor"))
)
</div>
</div>
<div class="row">
<div class="col medium">
<p>New Paths</p>
</div>
<div class="col medium">
<p></p>
</div>
<div class="col medium">
<p>Supervisor Paths</p>
</div>
</div>
<div class="row">
<div class="col medium">
#(Html.Kendo().Grid<Distribution.Models.Path>()
.Name("newPathsGrid")
.Columns(columns =>
{
columns.Bound(p => p.PathId).Visible(false);
columns.Bound(p => p.Title).Title(PathResource.Paths);
})
.Sortable()
.Scrollable()
.Navigatable()
.Filterable(filterable => filterable.Extra(false))
//.HtmlAttributes(new { style = "height:480px;" })
.Resizable(resize => resize.Columns(true))
.Selectable(s => s.Mode(GridSelectionMode.Multiple))
.DataSource(dataSource => dataSource
.Ajax()
//.PageSize(15)
.Events(events => events.Error("error_handler"))
.Model(model =>
{
model.Id(p => p.PathId);
model.Field(p => p.PathId).DefaultValue(new Guid());
})
.Read(read => read.Action("FillNewSupervisorPathsGrid", "Paths"))
)
)
</div>
<div class="col medium">
<input type="button" id="addPathToSupervisor" value=">>Add>>" />
<input type="button" id="removePathFromSupervisor" value="<<Remove<<" />
</div>
<div class="col medium k-rtl">
#(Html.Kendo().Grid<Distribution.Models.Path>()
.Name("supervisorPathGrid")
.Columns(columns =>
{
columns.Bound(p => p.PathId).Visible(false);
columns.Bound(p => p.Title).Title(PathResource.Paths);
})
//.Pageable()
.Sortable()
.Scrollable()
.Navigatable()
.Filterable(filterable => filterable.Extra(false))
//.HtmlAttributes(new { style = "height:480px;" })
.Resizable(resize => resize.Columns(true))
.Selectable(s => s.Mode(GridSelectionMode.Multiple))
.DataSource(dataSource => dataSource
.Ajax()
//.PageSize(15)
.Events(events => events.Error("error_handler"))
.Model(model =>
{
model.Id(p => p.PathId);
model.Field(p => p.PathId).DefaultValue(new Guid());
})
.Read(read => read.Action("FillSupervisorPathsGrid", "Paths", new { id = ViewBag.SupervisorId }))
)
)
</div>
</div>
this javascript code is to select Supervisor`s ID:
<script type="text/javascript">
function changeSupervisor(e) {
var id = this.value();
var supervisorPathGrid = $("#supervisorPathGrid").data("kendoGrid");
supervisorPathGrid.dataSource.read({ id: id });
}
and here is the javascript code to add and remove paths:
<script type="text/javascript">
var supervisorPathGrid = $("#supervisorPathGrid").data("kendoGrid");
var newPathsGrid = $("#newPathsGrid").data("kendoGrid");
var selectedItem = $("#supervisor").data("kendoComboBox");
$(document).on('click', '#addPathToSupervisor', function (e) {
e.preventDefault();
var supervisorId = selectedItem.value();
if (hasManyRowSelected(newPathsGrid)) {
var values = [];
values.push({
name: "supervisorId",
value: supervisorId
});
newPathsGrid.select().each(function () {
values.push({
name: "ids",
value: newPathsGrid.dataItem(this).PathId
});
});
$.ajax({
url: '#Url.Action("AddPathToSupervisor")',
type: 'POST',
datatype: "json",
traditional: true,
data: values,
success: function () {
newPathsGrid.select().each(function () {
var $this = $(this);
var data = newPathsGrid.dataItem($this);
supervisorPathGrid.dataSource.insert(0, data);
});
newPathsGrid.select().each(function () {
var $this = $(this);
var data = newPathsGrid.dataItem($this);
newPathsGrid.removeRow($this);
});
},
beforeSend: function () {
$('#addPathToSupervisor').attr("disabled", true);
$('#addPathToSupervisor').addClass("ajax-load");
},
error: function (event, request, settings) {
ajax_exception(event);
},
complete: function () {
$('#addPathToSupervisor').attr("disabled", false);
$('#addPathToSupervisor').removeClass("ajax-load");
grid.dataSource.read();
},
timeout: 50000
});
}
});
$(document).on('click', '#removePathFromSupervisor', function (e) {
e.preventDefault();
var supervisorId = selectedItem.value();
if (hasManyRowSelected(supervisorPathGrid)) {
var values = [];
supervisorPathGrid.select().each(function () {
values.push({
name: "ids",
value: supervisorPathGrid.dataItem(this).PathId
});
});
$.ajax({
url: '#Url.Action("RemovePathFromSupervisor")',
type: 'POST',
datatype: "json",
traditional: true,
data: values,
success: function () {
supervisorPathGrid.select().each(function () {
var $this = $(this);
var data = supervisorPathGrid.dataItem($this);
newPathsGrid.dataSource.insert(0, data);
});
supervisorPathGrid.select().each(function () {
var $this = $(this);
var data = supervisorPathGrid.dataItem($this);
supervisorPathGrid.removeRow($this);
});
},
beforeSend: function () {
$('#removePathFromSupervisor').attr("disabled", true);
$('#removePathFromSupervisor').addClass("ajax-load");
},
error: function (event, request, settings) {
ajax_exception(event);
},
complete: function () {
$('#removePathFromSupervisor').attr("disabled", false);
$('#removePathFromSupervisor').removeClass("ajax-load");
grid.dataSource.read();
},
timeout: 50000
});
}
});
now you need 2 Post methods to add and remove paths like this :
[HttpPost]
public ActionResult AddPathToSupervisor(string[] ids, string supervisorId)
{
try
{
PathsBll.AddPathsToSupervisor(ids, supervisorId);
}
catch (Exception ex)
{
throw ex;
}
return Json(ModelState.ToDataSourceResult());
}
[HttpPost]
public ActionResult RemovePathFromSupervisor(string[] ids)
{
try
{
PathsBll.RemovePathsFromSupervisor(ids);
}
catch (Exception ex)
{
throw ex;
}
return Json(ModelState.ToDataSourceResult());
}
in which you can write linq to add or remove paths through ids.
if you are familiar with kendo you know that you have 2 methods to fill each grid.
if you need more info add comment.
good lick

Related

Send true or false to database wether checkbox is checked or not

i got an issue regarding checkboxes with nedb. I want to send true or false if the checkbox is checked or not to the database i cannot solve this issue. i am working with node.js and nedb. please help!
client js eventlistener:
var taskDone = document.querySelectorAll('.taskDone');
taskDone.forEach(btn => {
btn.addEventListener('click', (e) => {
var done = e.target.attributes[1].value;
let id = e.target.getAttribute('data-id');
let isDone = document.querySelector(`input[data-id=${id}]`).value;
console.log(isDone + "isdone")
if ($(taskDone).is(':checked')) {
$('.text').addClass('line-through')
console.log("trues")
$.ajax({
url: 'http://localhost:3000/done/' + id,
type: 'PUT',
data: { isDone }
}).done(function (data) {
//location.reload()
console.log(data)
})
} else {
console.log('falses')
$('.text').removeClass('line-through')
}
})
})
update function to nedb:
function taskIsDone (id, done) {
return new Promise((resolve, reject) => {
db.update({ _id: id }, { $set: done }, { returnUpdatedDocs: true }, (err, num, updateDocs) => {
if (err) {
reject(err)
} else {
resolve(updateDocs)
}
})
})
}
server:
app.put('/done/:_id', async(req, res) => {
try {
var id = req.params._id;
let done = {
title: req.body.isDone,
}
const updateToDo = await taskIsDone(id, done)
console.log(updateToDo + " Todo done");
res.json(updateToDo);
} catch (error) {
res.json({error: error.message});
}
})
html/ejs:
<% for ( var i = 0; i < row.length; i++) { %>
<div class="edit-container" >
<input type="text" name="editTask" value="<%=row[i].title %>" data-id="<%=row[i]._id %>">
<button name="<%= row[i]._id %>" class="edit" data-id="<%=row[i]._id %>">save edit</button>
</div>
<div>
<input type="checkbox" name="isDone" class="taskDone" data-id="<%=row[i]._id %>">
<span class="text"><%= row[i].title %></span>
<button class="delete" name="<%= row[i]._id %>">delete</button>
</div>
<br>
<% } %>
i could really need some help with this! thanks
I have recreated a minimal example of what you are trying to do with checkbox checked state. I have added three checkboxes with same class name .taskDone
And i have using a change function not a click function. Every-time you clicked on the checkbox and check it will show the console log with checked and the data-id of that checkbox as well.
To get the data-id you can simply use .data function of jQuery and just specify what you want after the data-** to get it stored value.
In addition, do not use fat arrow - => function with jQuery. Use normal function statements so you can access you things by using $(this) instead of specifying each class or id
Live Working Demo:
let taskDone = document.querySelectorAll('.taskDone'); //get all the chechbox with same class .taskDone
taskDone.forEach(function(btn) { //use normal function
btn.addEventListener('change', function() {
let id = $(this).data('id') //get the data id of checkbox
if ($(this).is(':checked')) { //check if the clicked checkbox is checked or not
console.log(id + ' is Checked - Updating neDB') //console.log
$.ajax({
url: 'http://localhost:3000/done/' + id,
type: 'PUT',
data: 'isDone'
}).done(function(data) {
console.log(data)
})
} else {
console.log("Not Checked")
}
})
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="checkbox" name="isDone" class="taskDone" data-id="1">
<input type="checkbox" name="isDone" class="taskDone" data-id="2">
<input type="checkbox" name="isDone" class="taskDone" data-id="3">

search kendo multiselect without adding values to multiselect

Background: I have a kendo multiselect that gets populated with emails based on the values of a kendo dropdown. I also need to use the multiselect to 'search' for additional emails through our employee api. Then as i search and select new values to be added to the 'selected values' portion of the multiselect i want to be able to go back and see the initial populated values without the searched values.
Disclaimer: I can get all of this to work except the searched values get 'added' to the datasource which I dont want. Think of a temporary datasource when searching. So when i go to look through the initial populated values, the returned search vales are appended to the datasource values. Again, I do not want this.
CODE:
<div class="row display-row">
<div class="col-lg-4 col-md-4 col-sm-4 col-xs-12">
<h4>Location Group:</h4>
#(Html.Kendo().DropDownList()
.Name("weatherLocGroupNameDropDown")
.HtmlAttributes(new { style = "width:100%" })
.OptionLabel("Select location group...")
.DataTextField("LocationGroupName")
.DataValueField("LocationGroupId")
.DataSource(source =>
{
source.Read(read =>
{
read.Action("getLocationGroupNames", "Base");
});
})
)
</div>
<div class="col-lg-4 col-md-4 col-sm-4 col-xs-12">
<h4>Location:</h4>
#(Html.Kendo().DropDownList()
.Name("weatherLocNameDropDown")
.HtmlAttributes(new { style = "width:100%" })
.OptionLabel("Select location...")
.DataTextField("LocationName")
.DataValueField("LocationId")
.DataSource(source =>
{
source.Read(read =>
{
read.Action("getLocationNamesFilteredByLocationGroup", "Base")
.Data("filterLocation");
})
.ServerFiltering(true);
})
.Enable(false)
.AutoBind(false)
.Events(ev => ev.Change("populateLocGrpEmails"))
.CascadeFrom("weatherLocGroupNameDropDown")
)
</div>
<div class="row display-row">
<div class="col-lg-8 col-md-8 col-sm-8 col-xs-12">
#(Html.Kendo().MultiSelect()
.Name("recipientMultilist")
.Placeholder("Recipient(s)")
.AutoBind(false)
.Enable(false)
.HtmlAttributes(new { style = "width:100%" })
.DataTextField("EmailName")
.DataValueField("EmailId")
.Events(ev => ev.Filtering("searchEmails"))
)
</div>
</div>
function searchEmails() {
var searchText = $("#recipientMultilist").data('kendoMultiSelect').input.val();
searchText = searchText.trim();
if (searchText.length >= 3 && searchText != undefined && searchText != "") {
$.ajax(
{
url: "#Url.Action("getRecipientEmails", "Base")",
data: { searchTerm: searchText },
type: "GET",
dataType: "json",
async: false,
success: function (searchEmail) {
if (searchEmail.length > 0) {
for (var i = 0; i < searchEmail.length; i++) {
$('#recipientMultilist').data("kendoMultiSelect").dataSource.add({
EmailName: searchEmail[i].EmailName,
EmailId: searchEmail[i].EmailId
});
}
}
}, error: function (searchEmailErr) { console.log('searchEmailErr: ', searchEmailErr); }
})
}
}
function getLocationGroupEmails() {
return {
LocationGroupId: $("#weatherLocGroupNameDropDown").data("kendoDropDownList").value()
}
}
function filterLocation() {
return {
LocationGroupId: $("#weatherLocGroupNameDropDown").data("kendoDropDownList").value()
};
}
function populateLocGrpEmails() {
$("#recipientMultilist").data("kendoMultiSelect").enable();
tempMultiListStorage = [];
var locationText = $("#weatherLocNameDropDown").data('kendoDropDownList').text();
var locationGroupId = $("#weatherLocGroupNameDropDown").data('kendoDropDownList').value()
//get all emails associated with the location group and inserts into the recipientMultilist
$.ajax(
{
url: "#Url.Action("getEmailFilteredByLocationGroup", "Base")",
data: { LocationName: locationText, LocationGroupId: locationGroupId },
type: "GET",
dataType: "json",
async: false,
success: function (filteredEmail) {
if (filteredEmail.length > 0) {
for (var i = 0; i < filteredEmail.length; i++) {
$('#recipientMultilist').data("kendoMultiSelect").dataSource.add({
EmailName: filteredEmail[i].EmailName,
EmailId: filteredEmail[i].EmailId
});
tempMultiListStorage.push({
EmailName: filteredEmail[i].EmailName,
EmailId: filteredEmail[i].EmailId })
}
}
}, error: function (filteredEmailErr) { console.log('filteredEmailErr: ', filteredEmailErr); }
})
var multiselect = tempMultiListStorage
//"selects" the record that matches the location
var dropdownlist = $("#recipientMultilist").getKendoMultiSelect();
dropdownlist.value(locationText)
dropdownlist.trigger("change");
}
I do know that this code in searchEmails
$('#recipientMultilist').data("kendoMultiSelect").dataSource.add({
EmailName: searchEmail[i].EmailName,
EmailId: searchEmail[i].EmailId
});
is adding the values to the multiselect but thats there so i can at least test a few other things. Again, i am looking to 'see' the searched values, select the search values but not make them part of the 'datasource' by adding them.
I hope this was clear haha.
Can you give this a try and see if it works:
$("#multiselect").kendoMultiSelect({
select: function(e) {
e.preventDefault();
}
});

how to remove specific character and text

I have finish autocomplete with a jquery library which is
using jquery-ui-1.12.1.min.js
. I have modified it to make to get the search with username and full name. it will show as below image
when I select the value it will paste the whole text into an input box.
here is my question how do it modify it to show as the image but when I select the value it will only paste the username into input box?
how i only want nonstop00000 paste it into input box when i select the 1st value
here is my javascript
$(document).ready(function () {
$("#id").autocomplete({
source: function(request,response) {
$.ajax({
url: '#Url.Content("~/UserManagement/AutoCompleteUser")/',
type: "POST",
dataType: "json",
data: { term: request.term },
success: function (data) {
response($.map(data, function (item) {
return [{ label: item.Username + " | " + item.FullName, value: item.id }];
}))
}
})
},
messages: {
noResults: "", results: ""
}
});
})
here is my search controller
if (!String.IsNullOrEmpty(searchString))
{
user = user.Where(s => s.Username.Trim().Contains(searchString.Trim())
|| s.FullName.Trim().Contains(searchString.Trim()));
}
here is my autocomplete controller
public JsonResult AutoCompleteUser(string term)
{
var result = (from r in db.UserTables
where ((r.Status == "Active") && (r.Username.ToLower().Contains(term.ToLower()) || (r.FullName.ToLower().Contains(term.ToLower()))))
select new { Username = r.Username, FullName = r.FullName }).Distinct();
return Json(result);
}
here is my view
<div class="col-lg-9 col-md-9 col-sm-9 col-xs-12 search-panel">
#using (Html.BeginForm("Index", "UserManagement", FormMethod.Get))
{
<div class="input-group form-group ui-widget">
#Html.TextBox("id", ViewBag.CurrentFilter as string, new { #class = "form-control autocomplete", #placeholder = "Search for..." })
<span class="input-group-btn">
<input type="submit" value="Search" class="form-control autocomplete " />
</span>
</div>
}
</div>
To achieve this you can use the select event to amend the value to be placed in to the input. Try this:
$("#id").autocomplete({
// your settings...
select: function(e, ui) {
e.preventDefault();
$('#id').val(ui.item.label.split('|')[0].trim());
}
});

How to do autocomplete using values from the database in MVC 5?

At the moment, I have the following code:
main.js:
$(function () {
var keys = ["test1", "test2", "test3", "test4"];
$("#keywords-manual").autocomplete({
minLength: 2,
source: keys
});
});
Test.cshtml:
#model App.Models.Service
#{
ViewBag.Title = "Test";
}
<script src="~/Scripts/main.js"></script>
<h2>#ViewBag.Title.</h2>
<h3>#ViewBag.Message</h3>
#using (Html.BeginForm("SaveAndShare", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<h4>Create a new request.</h4>
<hr />
#Html.ValidationSummary("", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.ServiceType, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.ServiceType, new { #class = "form-control", #id = "keywords-manual" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Submit!" />
</div>
</div>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
The point is that currently I have just provided 4 constant values to the autocomplete. But then I created a database and a table named "services", which comes from my model named Service. I have already provided a few rows to the table with values. I have a field in my table called ServiceType, and I want the autocomplete to take the values of that column as a source. Please note that I have hosted my database in Azure and it is MySQL, though, I think it doesn't matter here. Can you tell me how can I take as a source the values of ServiceType column that is located inside my services table?
As far as I can tell by your question, it should look something like this:
$("#keywords-manual").autocomplete({
source: function (request, response) {
$.ajax({
url: "/Home/GetServiceTypes",
data: "{ 'keywords': '" + request.term + "' }",
dataType: "json",
type: "POST",
contentType: "application/json; charset=utf-8",
dataFilter: function (data) { return data; },
success: function (data) {
response($.map(data, function (item) {
return {
label: item.value,
value: item.value,
id: item.id
}
}))
}
});
},
minLength: 2
});
And the controller,
YourContext db = new YourContext();
public JsonResult GetServiceTypes() {
db.ServiceType.Where(s => keywords == null || s.Name.ToLower()
.Contains(keywords.ToLower()))
.Select(x => new { id = x.ServiceTypeID, value = x.ServiceTypeName }).Take(5).ToList();
return result;
}
Apologies for any typos, but that should be the jist of it. If you need to be searching for more than one keyword, in the controller method, split the value from 'keywords-manual' into a string array, then use a foreach loop or similar approach to match each value, adding matches to a total list each time.
** I say string array, that's pretty oldschool, split it into a list :)

How to Validating hidden input field of a form using jquery in MVC 4?

I am trying to Validated hidden input fields of a form in mvc4 but i failed. I try several ways but every time i failed. I searched in google and get some result but those are not work in my case. There is my code which I trying
#using (Html.BeginForm("NewSku","Product"))
{
#Html.AntiForgeryToken()
<label>Company Name :</label>
#(Html.Kendo().ComboBoxFor(x => x.CompanyId)
.Name("CompanyId")
.DataValueField("Id")
.DataTextField("CompanyName")
.Placeholder("Select Company")
.Filter(FilterType.Contains)
.DataSource(source => source.Read(read => read.Action("GetAllCompanys", "Product"))))
<br />
<label>Brand Name :</label>
#(Html.Kendo().ComboBoxFor(x => x.BrandId)
.Name("BrandId")
.DataTextField("BrandName")
.DataValueField("Id")
.Placeholder("Select Brand")
.DataSource(source => source.Read(read => read.Action("GetAllBrandsByCompany", "Product").Data("filterBrand")).ServerFiltering(true))
.AutoBind(false)
.CascadeFrom("company"))
<br />
<label>Product Name :</label>
#(Html.Kendo().ComboBoxFor(x => x.ProductId)
.HtmlAttributes(new { style = "Id=ProductComboBox" })
.Name("ProductId")
.DataTextField("ProductName")
.DataValueField("Id")
.Placeholder("Select Product")
.DataSource(source => source.Read(read => read.Action("GetAllProductsByBrand", "Product").Data("filterProduct")).ServerFiltering(true))
.AutoBind(false)
.CascadeFrom("brand"))
<br />
<label>Sku Name :</label>
#(Html.Kendo().ComboBoxFor(x => x.SkuId)
.Name("SkuId")
.HtmlAttributes(new { style = "Id=SkuComboBox" })
.DataTextField("SkuName")
.DataValueField("Id")
.Placeholder("Select Sku")
.DataSource(source => source.Read(read => read.Action("GetAllSkusByProduct", "Product").Data("filerSku")).ServerFiltering(true))
.AutoBind(false)
.CascadeFrom("product"))
<br />
<span id="ErrorMessagess" class="alert-error">
</span>
#Html.HiddenFor(x => x.CompanyName)
#Html.ValidationMessageFor(x=>x.CompanyName)
#Html.HiddenFor(x => x.BrandName)
#Html.ValidationMessageFor(x=>x.BrandName)
#Html.HiddenFor(x => x.ProductName)
#Html.ValidationMessageFor(x=>x.ProductName)
#Html.HiddenFor(x => x.SkuName)
#Html.ValidationMessageFor(x=>x.SkuName)
<input type="submit" class="k-button keep-right" value="Update" />
#Html.ValidationSummary(true)
}
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
<script>
$(document).ready(function () {
//$('form').validate().settings.ignore = [];
//$('form').validate({
// rules: {
// CompanyName: {
// required: true
// },
// BrandName: {
// required: true
// }
// }
//});
});
$('form').submit(function (e) {
var validator = $('form').data('validator');
validator.settings.ignore = "";
var errorList = $('form').validate().errorList;
console.log(errorList);
$("form").validate().form();
var form = $(this);
$("#CompanyName").val($("#CompanyId").data("kendoComboBox").text());
$("#BrandName").val($("#BrandId").data("kendoComboBox").text());
$("#ProductName").val($("#ProductId").data("kendoComboBox").text());
$("#SkuName").val($("#SkuId").data("kendoComboBox").text());
$.ajax({
url: form.attr("action"),
type: 'POST',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
data: JSON.stringify($('form').serializeJSON()),
success: function (result) {
// console.log(result);
$("#ErrorMessagess").val(result);
return;
}
});
e.preventDefault();
});
I am defiantly doing something wrong but I am does not understand what I am doing wrong.
You must do/add this in order to enable to validate hidden input:
$(document).ready(function () {
$.validator.setDefaults({
ignore: ""
});
});

Categories