Send a variable from Javascript to Javascript - javascript

I need to send a variable from a script to another script, i hope you can help me.
The variable i want to send is "campos" that is a counter.
This is the first script:
<script>
var campos = 0;
function agregarCampophp(hola){
// alert(campos);
var data = {};
data.pruebaId = $('#prueba').val();
data.deno = $('#subespecifica-descripcion').val();
data.prep = $('#partidaasociada-presupuestop').val();
data.tot = $('#partidaasociada-totalp').val();
// var datad = $.parseJSON(data);
// alert(data.prep);
campos = campos+1;
var NvoCampo= document.createElement("div");
$('#prueba').attr('value','');
$('#subespecifica-descripcion').attr('value','');
$('#partidaasociada-presupuestop').attr('value','');
$('#partidaasociada-totalp').attr('value','');
NvoCampo.id= "divcampo_"+(campos);
NvoCampo.innerHTML=
"<table class='table table-striped' >"+
" <tr>" +
" <td>" +
" <input type='text' size='20' name='articupart_" + campos +
"' id='articupart_" + campos + "' value='"+data.pruebaId+"'>" +
" </td>" +
" <td>" +
" <input type='text' size='70' name='articudeno_" + campos +
"' id='articudeno_" + campos + "' value='"+data.deno+"'>" +
" </td>" +
" <td align='right'>" +
" <input type='text' size='22' name='articuprep_" + campos +
"' id='articuprep_" + campos + "' value='"+data.prep+"'>" +
" </td>" +
" <td align='right'>" +
" <input type='text' size='22' name='articutot_" + campos +
"' id='articutot_" + campos + "' value='"+data.tot+"'>" +
" </td>" +
" <td align='right'>" +
" <input type='hidden' id='c_"+campos+"' value='hola_"+campos+"'>" +
" </td>" +
" </tr>"+
" </table>";
var contenedor= document.getElementById("contenedorcampos");
contenedor.appendChild(NvoCampo);
data.part = $('#articupart_'+campos).val();
data.deno = $('#articudeno_'+campos).val();
data.prep = $('#articuprep_'+campos).val();
data.tot = $('#articutot_'+campos).val();
}
I need that variable "campos" in this other Script:
$('._save').on('click', function(event){
event.preventDefault();
var data = {};
data.campos = $('#c_2').val();
alert(data.campos);
data.pruebaId = $('#prueba').val();
data.secId = $('#Hidden1').val();
data.proId = $('#Hidden2').val();
data.subId = $('#Hidden3').val();
data.proyId = $('#Hidden4').val();
data.actId = $('#Hidden5').val();
data.prep = $('#partidaasociada-presupuestop').val();
data.tot = $('#partidaasociada-totalp').val();
data.desc = $('#subespecifica-descripcion').val();
var success = function(data){
console.log("Success!", data);
}
var error = function(data){
console.log("Error!", data);
}
// $.ajax({
// url:'index.php?r=partidaasociada/get-linea',
// type:'POST',
// dataType:'json',
// data:data
// }, success, error);
});
I am trying to get the variable with a hidden div that i am creating in the first one, but i am getting some errors, thanks beforehand.

glad that worked for you. So basically this would be the solution:
// initialise the variable if it has not been initialised before
if (typeof(window.campos)=='undefined') {
window.campos= 0;
}
// increase by one
window.campos+=1;
And in your second script access it using:
$('._save').on('click', function(event){
...
// access the previously stored view counter
data.campos= window.campos;
}
Glad that worked for you!

Related

How can I check checkbox which is exists in model sepereted by coma from database (as shown below in code) using MVC asp.net?

Below is code of javascript. I want my checkboxes are selected based on coma seperated values from database. please let me know where i am mistaken
function GetStatesList() {
debugger;
var makeList = [];
var url = '/IAAISettings/GetStatesList';
$.ajax({
type: 'POST',
url: url,
success: function(stateList) {
var makeChkList = ""
for (var i = 0; i < stateList.length; i++) {
var st = stateList[i];
makeChkList += "<div class=\"col-12\">" +
"<label class=\"checkbox\">" +
"<input type=\"checkbox\" id=\"State_" + stateList[i] + "\" name=\"State_" + stateList[i] + "\" checked=\"" + #Model.States.Contains("Alaska") ? "checked" + "\" value=\"" + stateList[i] + "\">" +
"<i></i>" + stateList[i] +
"</label>" +
"</div>";
}
document.getElementById('StateschkList').innerHTML = makeChkList;
},
error: function(r) {
OnFailure(r);
},
failure: function(r) {
OnFailure(r);
}
});
}
I found issue. because of js is client side and model loads before js load it was not getting modal value and to get value we have to use this line
#Html.Raw(Json.Encode(Model.States));
function GetStatesList() {
debugger;
var arrstates = [];
var url = '/IAAISettings/GetStatesList';
$.ajax({
type: 'POST',
url: url,
success: function (stateList) {
var makeChkList = ""
var st =#Html.Raw(Json.Encode(Model.States));
arrstates = st.split(",");
console.log(st);
for (var i = 0; i < stateList.length; i++) {
var str = stateList[i];
if (arrstates.includes(stateList[i])) {
makeChkList += "<div class=\"col-12\">" +
"<label class=\"checkbox\">" +
"<input type=\"checkbox\" id=\"State_" + stateList[i] + "\" name=\"State_" + stateList[i] + "\" checked=checked\"" + "\" value=\"" + stateList[i] + "\">" +
"<i></i>" + stateList[i] +
"</label>" +
"</div>";
}
else {
makeChkList += "<div class=\"col-12\">" +
"<label class=\"checkbox\">" +
"<input type=\"checkbox\" id=\"State_" + i + "\" name=\"State_" + i + "\" value=\"" + stateList[i] + "\">" +
"<i></i>" + stateList[i] +
"</label>" +
"</div>";
}
}
document.getElementById('StateschkList').innerHTML = makeChkList;
},
error: function (r) {
OnFailure(r);
},
failure: function (r) {
OnFailure(r);
}
});
}

How to dropdown bind according to this record

I have two loops:
table loop: fills the table rows
dropdown loop: fills the dropdowns with typeid.data[i].TypeId, add a select in the last dropdown
My dropdown does not fill according to its record and I don't understand why.
var QuestionId = data[i].QuestionOid;
var fkid = data[i].FkSource;
var selectdata = data[i];
var selectinnerhtml = "<span><select id = \"answer" + QuestionId + "\" name = \"answer" + QuestionId + "\" class=\"answer" + QuestionId + " form-control input-small\" > </select></span>";
$.ajax({
type: "GET",
dataType: 'json',
contentType: 'application/json; charset=utf-8',
url: '/MYaPI/EmployeeDetails/' + data[i].TypeId,
success: function(datasa) {
var optionhtmls = '<option value="' +
0 + '">' + "--Select--" + '</option>';
$(".answer" + QuestionId).append(optionhtmls);
$.each(datasa, function(j) {
var optionhtmls = '<option value="' +
datasa[j].Oid + '">' + datasa[j].Title + '</option>';
$(".answer" + QuestionId).append(optionhtmls);
});
}
});
var newRows2select = "<tr class='rows'><a href = '' >" +
" <td QuestionCategoryTitle = " + selectdata.QuestionCategoryTitle + " QuestionHeader = " + selectdata.QuestionHeader + " ContentTypeId=" + selectdata.FkSource + " QuestionTypeId=" + selectdata.FkQuestionType + " QuestionOID=" + selectdata.QuestionOid + " CategoryOID=" + selectdata.FkQuestionCategory + " class=\"question-block\"><small style=\"color:slateblue;font-weight: bolder;display:none\">CATEGORY: " + selectdata.QuestionCategoryTitle + ",</small>" +
" <i class=\"deleteRow fas fa-trash float-right\"></i> " +
"<p> " + selectdata.QuestionHeader + "</p>" + selectinnerhtml + " </td></a> \"</tr>";
$("#table23").append(newRows2select);
I don't know the jQuery way, however it is easily achievable with plain JavaScript
var select = document.createElement("select");
var selectinnerhtml = document.createElement("span");
selectinnerhtml.appendChild(select);
$.each(datasa, function(j) {
//create a new option element
var option = document.createElement("option");
//set the value attribute
option.setAttribute("value", datasa[j].oid);
//fill the HTML tag
option.value = datasa[j].Title
//add the option to the select dropdown
select.add(option);
});

Javascript button delete and edit

I can add a user just fine, but when I try to delete or edit someone my button doesn't work. I tried to put an alert to see what's wrong but I can't find anything.
As you can see it creates the button delete and edit when you add someone to the table.
Here's my js code:
var nextId = 1;
var activeId = 0;
$(document).ready(function() {
$("#addBtn").on("click", function() {
var name = $("#txtNome").val();
if (name.trim().length < 3 || $("#txtIdade").val() == "") {
alert("Dados Incorretos, Por favor, digite o seu nome e idade.");
$("#txtNome").focus();
return false;
} else {
addt();
formClear();
}
});
$("#btnDelete").on("click", function(delete_button) {
$(delete_button).parents("tr").remove();
});
$("#btnEdit").click(function() {
var row = $(edit_button).parents("tr");
var cols = row.children("td");
activeId = $($(cols[2]).children("button")[0]).data("id");
$("#txtNome").val($(cols[0]).text());
$("#txtIdade").val($(cols[1]).text());
$("#edt").css("display", "inline-block");
$("#addBtn").prop("disabled", true);
})
function addt() {
if ($("#tblUser tbody").length == 0) {
$("#tblUser").append("<tbody></tbody>");
}
$("#tblUser tbody").append(addUserToTable(nextId));
nextId += 1;
}
function addUserToTable(id) {
var row =
"<tr>" +
"<td>" + $("#txtNome").val() + "</td>" +
"<td>" + $("#txtIdade").val() + "</td>" +
"<td>" +
"<button type='button' style='margin-right:20px;' " + "id='btnEdit' " + "class='btn btn-default'" + "data-id='" + nextId + "'>" + "<span class='glyphicon glyphicon-edit'></span>" +
"</button>" +
"<button type='button' " + "id='btnDelete'" + "class='btn btn-default'" + "data-id='" + nextId + "'>" + "<span class='glyphicon glyphicon-remove'></span>" +
"</button>" +
"</td>" +
"</tr>"
return row;
nextId += 1;
}
function formClear() {
$("#txtNome").val("");
$("#txtIdade").val("");
}
function attTable(id) {
var row = $("#tblUser button[data-id='" + id + "']").parents("tr")[0];
$(row).after(addUserToTable());
$(row).remove();
formClear();
$("#edt").css("display", "none");
}
});
As you are adding String content(in form of HTML) to a table, so these are the new content for document, and Jquery has bind all event at time of page load,
So when you add dynamic content to table, you explicitly need to bind the events to DOM elements (in your case edit and delete button)
Please try following code,
<script>
var nextId = 1;
var activeId = 0;
$(document).ready(function(){
$("#addBtn").on("click",function(){
var name = $("#txtNome").val();
if (name.trim().length < 3 || $("#txtIdade").val() == "") {
alert("Dados Incorretos, Por favor, digite o seu nome e idade.");
$("#txtNome").focus();
return false;
} else {
addt();
formClear();
}
});
$("#btnDelete").on("click",deletebutton);
function deletebutton(){
$(this).parents("tr").remove();
}
$("#btnEdit").click(editButton);
function editButton(){
var row = $(this).parents("tr");
var cols = row.children("td");
activeId = $($(cols[2]).children("button")[0]).data("id");
$("#txtNome").val($(cols[0]).text());
$("#txtIdade").val($(cols[1]).text());
$("#edt").css("display", "inline-block");
$("#addBtn").prop("disabled" , true);
}
function addt(){
if ($("#tblUser tbody").length == 0) {
$("#tblUser").append("<tbody></tbody>");
}
var btn = $(addUserToTable(nextId));
$(btn).find("#btnEdit").bind('click', editButton);
$(btn).find("#btnDelete").bind('click', deletebutton);
$("#tblUser tbody").append($(btn));
nextId += 1;
}
function addUserToTable(id) {
var row =
"<tr>" +
"<td>" + $("#txtNome").val() + "</td>" +
"<td>" + $("#txtIdade").val() + "</td>" +
"<td>" +
"<button type='button' style='margin-right:20px;' " + "id='btnEdit' " + "class='btn btn-default'" + "data-id='" + nextId + "'>" + "<span class='glyphicon glyphicon-edit'></span>" +
"Edit </button>" +
"<button type='button' " + "id='btnDelete'" + "class='btn btn-default'" + "data-id='" + nextId + "'>" + "<span class='glyphicon glyphicon-remove'></span>" +
"Delete</button>" +
"</td>" +
"</tr>"
return row;
nextId += 1;
}
function formClear() {
$("#txtNome").val("");
$("#txtIdade").val("");
}
function attTable(id) {
var row = $("#tblUser button[data-id='" + id + "']").parents("tr")[0];
$(row).after(addUserToTable());
$(row).remove();
formClear();
$("#edt").css("display", "none");
}
});
</script>
I hope this will solve your problem
I have tried with following HTML content
<table id="tblUser"></table>
<input type="button" id="addBtn" value="addBtn"/><br/>
<input type="button" id="btnDelete" value="btnDelete"/><br/>
<input type="button" id="btnEdit" value="btnEdit"/><br/>
txtNome: <input type="text" id="txtNome" value="Name 1" /><br/>
txtIdade: <input type="text" id="txtIdade" value="Name 2"/><br/>

Pushing to null array, even though it is called as a global and works in my localhost, but not on the server

I'm getting the following error in my javascript. As the title states, this code is working on my localhost, but not when I put it onto my server
Uncaught TypeError: Cannot read property 'push' of null
at addItemToCart (shoppingCart.js:36)
at HTMLButtonElement.<anonymous> (shoppingCart.js:193)
at HTMLDocument.dispatch (jquery-1.11.1.min.js:3)
at HTMLDocument.r.handle (jquery-1.11.1.min.js:3)
I have identified that just before this push function is called that the cart variable is indeed null, but I declare it globally as an empty array, so I'm not sure why that would be.
Code for the function its failing on
var cart = [];
function addItemToCart(name, price, count, id, shortName) {
for (var i in cart) {
if (cart[i].id === id) {
cart[i].count += count;
saveCart();
for (var i in cart) {
if (cart[i].id == 500 && id != 500) {
removeItemFromCart(500);
alert('because you changed your cart, you will have to reapply your coupon');
}
}
return;
}
}
for (var i in cart) {
if (cart[i].id == 500 && id != 500) {
removeItemFromCart(500);
alert('because you changed your cart, you will have to reapply your coupon');
}
}
var item = new Item(name, price, count, id, shortName);
console.log(cart);
cart.push(item);
saveCart();
}
The error happens at teh cart.push(item); line because cart is null and can not be pushed to. Anymore information someone may need to help feel free and I will shoot it your way.
Thanks in advance!!!
edit:
function displayCart() {
console.log("*** display Cart ***");
var cartArray = listCart();
var output = "<tr><th>Items</th><th>Quantity</th><th>Price</th></tr>";
var output2 = "<tr><th> </th><th>Product name</th><th>Product price</th><th>Quantity</th><th>Total</th></tr>";
var output3 = " <tr><th>Product(s)</th></tr>";
var output4 = "";
console.log(listCart());
for (var i in cartArray) {
output += "<tr class='item'><td><div class='delete' id='removeItem' data-id='" + cartArray[i].id + "'></div>" + cartArray[i].name + "</td><td><input type='text' value='" + cartArray[i].count + "' readonly></td> <td class='price'>" + cartArray[i].price + "</td></tr>"
output2 += "<tr class='item'>"
+ "<td class='thumb'><a href='" + cartArray[i].id + "-item.php'><img src='img/catalog/product-gallery/" + cartArray[i].id + ".png' alt='Product Image'/></a></td>"
+ "<td class='name'><a href='" + cartArray[i].id + "'-item.php'>" + cartArray[i].name + "</a></td>"
+ "<td class='price'>$" + cartArray[i].price + "</td>"
+ "<td class='qnt-count'>"
+ "<a class='incr-btn' href='#' id='oneless' data-id='" + cartArray[i].id + "'>-</a>"
+ "<input class='quantity form-control' type='text' value=' " + cartArray[i].count + " '>"
+ "<a class='incr-btn' id='onemore' data-productid='" + cartArray[i].id + "' data-name='" + cartArray[i].name + "' data-quantity='" + cartArray[i].count + "' href='#'>+</a>"
+ "</td>"
+ "<td class='total'>$<em id='test'>" + cartArray[i].total + "</em></td>"
+ "<td class='delete' id='removeAllFromCart' data-id='" + cartArray[i].id + "'><i class='icon-delete'></i></td>"
+ "</tr>";
output3 += " <tr><td class='name border'>" + cartArray[i].shortName + "<span>x" + cartArray[i].count + "</span></td>"
+ "<td class='price border'>$" + cartArray[i].total + "</td></tr>";
if ($("#offerCount").attr("data-id") == cartArray[i].id) {
output4 += +"<a class='incr-btn' href='#' id='oneless' data-id='" + cartArray[i].id + "'>-</a>"
+ "<input class='quantity form-control' type='text' value=' " + cartArray[i].count + " '>"
+ "<a class='incr-btn' id='onemore' data-productid='" + cartArray[i].id + "' data-name='" + cartArray[i].name + "' data-quantity='" + cartArray[i].count + "' href='#'>+</a>";
}
}
output3 += " <tr><td class='th border'>Shipping</td><td class='align-r border'>Free shipping</td></tr>"
+ "<tr><td class='th'>Order total</td><td class='price'>$" + totalCart() + "</td></tr>"
$("#offerCount").html(output4);
$("#productDisplay").html(output3);
$("#showFullCart").html(output2);
$("#showCart").html(output);
$("#cartTotal").html(totalCart());
$("#totalCart").html(totalCart());
$("#myCartTotal").html(totalCart());
$("#showmyTotal").html(totalCart());
$("#cartCount").html(countCart());
}
function addCouponToCart(coupon) {
if (coupon == 'coupon10' && couponsAdded == 0) {
var couponReduce = -(totalCart() * .1).toFixed(2);
addItemToCart('10% off Coupon', couponReduce, 1, 500, '10% off');
couponsAdded += 1;
saveCoupon();
}
displayCart();
}
function countCart() {
var totalCount = 0;
for (var i in cart) {
totalCount += cart[i].count;
}
return totalCount;
}
function removeItemFromCartAll(id) {
for (var i in cart) {
if (cart[i].id === id) {
cart.splice(i, 1);
break;
}
}
for (var i in cart) {
if (cart[i].id == 500 && id != 500) {
removeItemFromCart(500);
alert('because you changed your cart, you will have to reapply your coupon');
}
}
saveCart();
}
Code that calls the addCouponToCart Function whenever a post gets set.
<?php if (isset($_POST['coupon_code'])) { ?>
<script>
addCouponToCart(coupon);
</script>
<?php } ?>
#codenoname Provided the correct answer of checking for the null cart. That solved the issue, ultimately a lot of functions were not being defined properly. I had wrapped the entire code in a document ready function which seemed to be the issue. Whenever I removed that it worked. Thank you all for your input.
if (!cart) {
cart = [];
}

AngularJS: function not defined error when html is generated from Javascript

I amd dynamically generating my edit table from javaScript. But function mentioned via it are not recognaizeable from AngularJS, for that, I am not able to remove and edit my rows.
Here HvacStandards() and checkemail() and showInvite() are only working. for other's i am getting following exception in chorom console:
Errors:
Uncaught ReferenceError: editRow is not defined
Uncaught ReferenceError: deleteRow is not defined
AngularJs code
$scope.HvacStandards = function () {
var rowID = $scope.newRow;
if ($scope.currentRow > 0) {
saveEdits();
} else {
var teamName = $("#teamName").val();
var email = $("#email").val();
var sHtml = "<tr id='row"+rowID+"'>"
+ "<td id=\"tno" + rowID + "\">" + rowID
+ "</td>"
+ "<td id=\"tName" + rowID + "\">" + teamName
+ "</td>" + "<td id=\"mail" + rowID + "\">" + email
+ "</td>" + "<td><button onclick='editRow(" + rowID
+ ")'>Edit</button> " + "<button onclick='deleteRow("
+ rowID + ")'>Delete</button>" + "</tr>";
$("#inviteTable").append(sHtml);
$scope.newRow++;
$("#teamName,#email").val("");
$scope.emails[$scope.number]=$scope.formData.email;
}
};
$scope.editRow = function(rowID) {
$('#teamName').val($('#tName' + rowID).html());
$('#email').val($('#mail' + rowID).html());
$scope.currentRow = rowID;
};
$scope.saveEdits = function (){
$('#teamName' + currentRow).html($('#tName').val());
$('#email' + currentRow).html($('#mail').val());
$("#teamName,#email").val("");
$scope.currentRow = -1;
};
$scope.deleteRow = function (rowID) {
$('#row' + rowID).remove();
};
$scope.showInvite = function(){
$scope.show = true;
$scope.formData = {};
};
and my html is like following:
<div name="showInviteDiv" id="showInviteDiv">
<a ng-click="showInvite()" ng-show="!show">Invite a Team</a>
</div>
<div class="invite-form" name="inviteTeamDiv" id="inviteTeamDiv" ng-show="show">
<form ng-submit="HvacStandards()" novalidate class="css-form"
name='inviteTeamsForm'>
<div>
<input type="text" name="teamName" ng-model="formData.teamName" id="teamName"
placeholder="Team Name" style="width: 38%;" required="required"
ng-minlength="6" ng-maxlength="30">
<input
type="text" name="email" ng-model="formData.email" id="email"
placeholder="Email" style="width: 38%;" required="required"
ng-pattern="/^[a-z]+[a-z0-9._]+#[a-z]+\.[a-z.]{2,5}$/" ng-change="checkEmail(form Data.email)" ng-blur="checkEmail(formData.email)">
</div>
<div>
<input type="submit" value="Invite Team"
ng-disabled="inviteTeamsForm.$invalid || isEmailExists"> or <a ng-click="hideInvite()">I'm done sending invites</a>
</div>
In the Table that you are creating, you used onclick instead of ng-click. You need to use ng-click in order to use scope function.
So your HvacStandards function will look like this:
$scope.HvacStandards = function () {
var rowID = $scope.newRow;
if ($scope.currentRow > 0) {
saveEdits();
} else {
var teamName = $("#teamName").val();
var email = $("#email").val();
var sHtml = "<tr id='row"+rowID+"'>"
+ "<td id=\"tno" + rowID + "\">" + rowID
+ "</td>"
+ "<td id=\"tName" + rowID + "\">" + teamName
+ "</td>" + "<td id=\"mail" + rowID + "\">" + email
+ "</td>" + "<td><button ng-click='editRow(" + rowID
+ ")'>Edit</button> " + "<button ng-click='deleteRow("
+ rowID + ")'>Delete</button>" + "</tr>";
$("#inviteTable").append(sHtml);
$scope.newRow++;
$("#teamName,#email").val("");
$scope.emails[$scope.number]=$scope.formData.email;
}
};
$scope.HvacStandards = function () {
var rowID = $scope.newRow;
if ($scope.currentRow > 0) {
saveEdits();
} else {
var teamName = $("#teamName").val();
var email = $("#email").val();
var sHtml = "<tr id='row"+rowID+"'>"
+ "<td id=\"tno" + rowID + "\">" + rowID
+ "</td>"
+ "<td id=\"tName" + rowID + "\">" + teamName
+ "</td>" + "<td id=\"mail" + rowID + "\">" + email
+ "</td>" + "<td><button ng-click='editRow(" + rowID
+ ")'>Edit</button> " + "<button ng-click='deleteRow("
+ rowID + ")'>Delete</button>" + "</tr>";
//$("#inviteTable").append(sHtml); // don't do this
// use compile in following manner
$("#inviteTable").append(
$compile(sHtml)($scope)
);
$scope.newRow++;
$("#teamName,#email").val("");
$scope.emails[$scope.number]=$scope.formData.email;
}
};
try this
$scope.HvacStandards = function () {
var rowID = $scope.newRow;
if ($scope.currentRow > 0) {
saveEdits();
} else {
var teamName = $("#teamName").val();
var email = $("#email").val();
var sHtml = "<tr id='row"+rowID+"'>"
+ "<td id=\"tno" + rowID + "\">" + rowID
+ "</td>"
+ "<td id=\"tName" + rowID + "\">" + teamName
+ "</td>" + "<td id=\"mail" + rowID + "\">" + email
+ "</td>" + "<td><button ng-click='editRow(" + rowID
+ ")'>Edit</button> " + "<button ng-click='deleteRow("
+ rowID + ")'>Delete</button>" + "</tr>";
// $compile(sHtml)($scope) aissign to variable:
let shown = $compile(sHtml)($scope);
$("#inviteTable").append(
shown;
);
$scope.newRow++;
$("#teamName,#email").val("");
$scope.emails[$scope.number]=$scope.formData.email;
}
};

Categories