Selectpicker doesn't show inside a table column - javascript

I have a table of movies and I am using ng-repeat to display the rows. But each column shows the movie variable (like name, director, actor, year..) and one of then is the genre but the movie can have multiple genres. So, I am using a select to do this.
The problem is, my select just don't show up in the screen! I don't know if this is a angular problem or I am doing something wrong..
Here is my table:
<div class="container" style="margin-top: 30px; min-width: 90%" ng-controller="adminController">
<table class="table table-hover">
<thead>
<tr>
<th>
<h2>Filme</h2>
</th>
<th>
<h2>Ano</h2>
</th>
<th>
<h2>Diretor</h2>
</th>
<th>
<h2>Ator/Atriz</h2>
</th>
<th>
<h2>Pontuação</h2>
</th>
<th>
<h2>Generos</h2>
</th>
<th>
</th>
</tr>
</thead>
<tbody>
<tr>
<td><input class="form-control" ng-model="novoFilme.nome" placeholder="Nome do filme"/></td>
<td><input class="form-control" ng-model="novoFilme.ano" placeholder="Ano do filme"/></td>
<td><input class="form-control" ng-model="novoFilme.diretor" placeholder="Diretor do filme"/></td>
<td><input class="form-control" ng-model="novoFilme.ator" placeholder="Ator/atriz do filme"/></td>
<td><input class="form-control" ng-model="novoFilme.pontuacao" placeholder="Pontuação do filme"/></td>
<td>
<select class="selectpicker" title="Nenhum selecionado" ng-model="novoFilme.generos" ng-options="genero.value as genero.nome for genero in generos" multiple>
</select>
</td>
<td style="text-align: right"><button class="btn btn-primary" ng-click="addFilme()">Adicionar Filme</button></td>
</tr>
<tr ng-repeat="filme in filmes">
<td><input class="form-control" ng-model="filme.nome"/></td>
<td><input class="form-control" ng-model="filme.ano"/></td>
<td><input class="form-control" ng-model="filme.diretor"/></td>
<td><input class="form-control" ng-model="filme.ator"/></td>
<td><input class="form-control" ng-model="filme.pontuacao"/></td>
<td>
<select class="selectpicker" title="Nenhum selecionado" ng-options="genero.value as genero.nome for genero in generos" multiple>
</select>
</td>
<td style="text-align: right"><button class="btn btn-danger" ng-click="remove(filme._id)">Remover</button></td>
</tr>
</tbody>
</table>
</div>
Here is my controller:
app.controller('adminController', function ($scope, $http) {
$scope.generos = [
{
nome: "Comédia",
value: "Comédia"
},
{
nome: "Comédia Romântica",
value: "Comédia Romântica"
},
{
nome: "Drama",
value: "Drama"
},
{
nome: "Ação",
value: "Ação"
},
{
nome: "Policial",
value: "Policial"
},
{
nome: "Suspense",
value: "Suspense"
},
{
nome: "Terror",
value: "Terror"
}
];
var atualizar = function () {
$http.get('/filmes').success(function (response) {
$scope.filmes = response;
$scope.novoFilme = "";
});
};
atualizar();
$scope.addFilme = function () {
$http.post('/filmes', $scope.novoFilme).success(function (response) {
atualizar();
});
};
$scope.remove = function (id) {
$http.delete('/filmes/' + id).success(function (response) {
atualizar();
});
};
$scope.save = function () {
for (i = 0; i < $scope.filmes.length; i++) {
$http.put('/filmes/' + $scope.filmes[i]._id, $scope.filmes[i]).success(function (response) {
atualizar();
});
}
};
});
Anyone knows how to fix it or any ideas how to do this in a different way?
Edit:
Here is the screen shot:
http://imgur.com/a/bXgBK

Well, ngOptions requires ngModel directive, so you have to use it in your <select> tag.
If you look at your console (pressing F12) you'll see this error:
Controller 'ngModel', required by directive 'ngOptions', can't be
found!
So, Instead of:
<select class="selectpicker" title="Nenhum selecionado"
ng-options="genero.value as genero.nome for genero in generos" multiple>
Use:
<select ng-model="genero" class="selectpicker" title="Nenhum selecionado"
ng-options="genero.value as genero.nome for genero in generos" multiple>
Tip: Always look at your console (F12) to see the errors when something went wrong.

I found that the problem is just the select class, in this case class="selectpicker", I don't know why this is causing issue, but if you remove this class will work fine.
Just to know, this class is a class from Silvio Moreto bootstrap select that you can find in the link below.
https://silviomoreto.github.io/bootstrap-select/

Related

select2 run trigger select once only

I would like to ask for you help. I am currently having a problem in select2/jquery. I've been solving this for 1 day now.
Here is the scenario:
Inventory Table
I have a customer code and a customer name field(select2).
I can type on the customer code field, and once I selected a customer code, the customer name field will be automatically filled out.
The same with the customer name field, If I selected a customer name, the customer code field will be filled out.
Now, I already got it working. The problem is, I have a code that will automatically fill each field. But what happens is, it seems that if I change the customer name field, it will fill out the customer code field, and it will trigger again to change the customer name field, and it goes on and on. They keep on changing each other. Like a never ending loop.
My question is, how can I trigger it only once, so it would not continue in a loop.
If I change the customer code field, it would fill out the customer name field. Stop.
If I change the customer name field, it would fill out the customer code field. Stop.
Hoping for you guidance. Thank you everyone. Here is my code:
$('body').on('select2:select', '.select-customer-code', function (e) {
var data = e.params.data;
var customer_code = data.id;
var parent = $(this);
/** Load Ajax */
ajax_loader(baseUrl + 'merchandiser/inventory/manual/customer/info/fetch', {
customer_code: customer_code
}).done(function (response) {
var response = $.parseJSON(response);
var el = parent.parent().parent('tr').find('.select-customer-name');
/** Load select2 */
select2_loader_plain(el, customer_name_url);
el.select2('trigger', 'select', {
data: {
id: response.customer_code,
text: response.customer_name
}
});
});
});
$('body').on('select2:select', '.select-customer-name', function (e) {
var data = e.params.data;
var parent = $(this);
var el = parent.parent().parent('tr').find('.select-customer-code');
el.select2('trigger', 'select', {
data: {
id: data.id,
text: data.id
}
});
});
EDIT: (ADDED THE HTML MARKUP)
<table width="100%" class="table table-condensed">
<thead>
<tr>
<th><input type="checkbox"></th>
<th>#</th>
<th>Customer Code</th>
<th>Customer Name</th>
<th>Inventory Date</th>
<th>Material Code</th>
<th>Material Description</th>
<th>UOM</th>
<th>QTY</th>
<th>Batch</th>
<th>Reported By</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="checkbox"></td>
<td>1</td>
<td><select class="form-control form-select select-customer-code"></select></td>
<td><select class="form-control form-select select-customer-name"></select></td>
<td><input type="text" class="form-control"></td>
<td><input type="text" class="form-control"></td>
<td><input type="text" class="form-control"></td>
<td>
<select name="" id="" class="form-control">
<option value=""></option>
</select>
</td>
<td><input type="text" class="form-control"></td>
<td><input type="text" class="form-control"></td>
<td><input type="text" class="form-control"></td>
</tr>
<tr>
<td><input type="checkbox"></td>
<td>2</td>
<td><select class="form-control form-select select-customer-code"></select></td>
<td><select class="form-control form-select select-customer-name"></select></td>
<td><input type="text" class="form-control"></td>
<td><input type="text" class="form-control"></td>
<td><input type="text" class="form-control"></td>
<td>
<select name="" id="" class="form-control">
<option value=""></option>
</select>
</td>
<td><input type="text" class="form-control"></td>
<td><input type="text" class="form-control"></td>
<td><input type="text" class="form-control"></td>
</tr>
</tbody>
</table>
EDIT 2: SOLUTION (answered by Tom Jenkins)
var code_lock = false;
$('.select-customer-code').on('select2:select', function (e) {
var data = e.params.data;
var customer_code = data.id;
var customer_name = $('.select-customer-name');
if (!code_lock) {
/** Load Ajax */
ajax_loader(baseUrl + 'merchandiser/inventory/manual/customer/info/fetch', {
customer_code: customer_code
}).done(function (response) {
var response = $.parseJSON(response);
customer_name.select2('trigger', 'select', {
data: {
id: response.customer_code,
text: response.customer_name
}
});
});
}
});
$('.select-customer-name').on('select2:select', function (e) {
var data = e.params.data;
var parent = $(this);
var customer_code = $('.select-customer-code');
customer_code.select2('trigger', 'select', {
data: {
id: data.id,
text: data.id
}
});
code_lock = true;
setTimeout(function () {
code_lock = false;
}, 1000);
});
What if you were to add a global variable called recentlyChanged then before you make a change, you check to see if it was recently changed. If it was recently changed, you do not make the changes. If it was not recently changed, set the global variable to show it was recently changed, and use setTimeout to delay changing it back to not being recently changed. Does that make sense?

Angular get value in the app component coming from a map

I tried several ways and I was unable to print the valueM, ValueR and product in my app.component.html
Can anyone give me a solution or tip for me to proceed?
thank you very much
app.component.ts
forkJoin(
this.service1.method1(filter1),
this.service2.methodo2(filter2),
).subscribe(data => {
const cc = data[0] || [];
console.log(cc);
const pp = data[1] || [];
const arrayIdsProducts = pp.map(element => element.product);
const sc = cc.filter(elemente => arrayIdsProducts.includes(elemente.product));
console.log(sc);
this.valuesC = sc.map(e => ({
valueM: e.valueM,
valueR: e.valueR,
product: e.product
}));
Console.log
[{…}] 0: codigoSistemaFormatado: "0002.0004", id: 119 product: 5, productName: "T-SHIRT XYZ BLUE XL"
[{…}] 0: product: 5, ValueM: 31.053333333333335, valorR: 49.9
app.compontent.html
<table formArrayName="productos" class="table table-bordered table-striped">
<thead>
<tr>
<th width="5%" class="text-center">ValueM</th>
<th width="30%" class="text-center">ValueR</th>
<th width="9%" class="text-center">Produte</th>
<th width="7%"></th>
</tr>
</thead>
<tr [formGroupName]="i" *ngFor="let item of formGroup.controls.produtos.controls; let i=index; last as isLast; first as isFirst">
<td>
{{i+1}}
</td>
<input hidden formControlName="unidadeNome">
<input hidden formControlName="codigoSistemaFormatado">
<input hidden formControlName="ValueM"> >
<input hidden formControlName="valueR">
<td>
<app-vo-filtro-produtos (valueSelected)="onProductChanged($event, i)" [showLabel]="false" [multiple]="false"
formControlName="produto" [itens]="produtos[i]"></app-vo-filtro-produtos>
</td>
<td>
<input type="text" value="{{produtos[i].codigoSistemaFormatado}}" class="form-control" disabled="true">
</td>
<td>
<input type="text" value="{{produtos[i].unidadePrincipal?.unidade.sigla}}" class="form-control" disabled="true">
</td>
<td>
<input type="text" value="{{valueM HERE}}" class="form-control" disabled="true">
</td>
<td>
<input type="text" value="{{valueR HERE}}" class="form-control" disabled="true">
</td>
</td>
</tr>
</table>
</form>
As you need to access valuesC value which is assigned dynamically, you should access it like:
<input type="text" [value]="valuesC.valueR" >
Update:
If I understood it correctly, you will be having separate array as valueC which will have mapping of productId values with valueR and valueC.
Then better to write separate methods in our ts file to return product specific valu from valueC array like this:
ts file:
getValue(productId, key) {
for(let product of this.valueC) {
if(productId == product.product) {
return product[key];
}
}
}
Read in html file like:
<input type="text" [value] = "getValue(<REPLCAE_WITH_PRODUCT_ID>, 'valueM')">
<input type="text" [value] = "getValue(<REPLCAE_WITH_PRODUCT_ID>, 'valueR')">

How to set value in session storage with vueJS by ajax dropdown select to other changes value

when i select dropdown option and change the other value as product code. i fetch the problem that when click add button to set this value in session storage with vuejs it didn't work, but when i fill up the field by type this time value set in session storage.
i tried with ajax to change the value by selecting product code and set to it value in session storage with vuejs.
This is front page code:
<div id="app">
<table class="table table-sm">
<tr>
<th>Product Code</th>
<th>Product Name</th>
<th>Product Price</th>
<th>Action</th>
</tr>
<tr>
<td>
<select v-model="newCat.mtype" id="" class="form-control-sm form-control code">
<?php
$db = new mysqli("localhost","root","","test");
$select = $db->query("select code from product");
while (list($code) = $select->fetch_row()) {
echo "<option value='$code'>$code</option>";
}
?>
</select>
</td>
<td>
<input type="text" v-model="newCat.name" class="form-control-sm form-control name"/>
</td>
<td>
<input type="text" v-model="newCat.rules" class="form-control-sm form-control price"/>
</td>
<td>
<button class="btn btn-sm" #click="addCat">+</button>
</td>
</tr>
</table>
<br>
<div class="table-responsive">
<table class="table table-sm">
<tr>
<th>Code</th>
<th>Product Name</th>
<th>Product Price</th>
<th class="text-center">Action</th>
</tr>
<tr v-for="(cat, n) in cats">
<td>{{cat.mtype}}</td>
<td>{{cat.name}}</td>
<td>{{cat.rules}}</td>
<td class="text-center"><button #click="removeCat(n)" class="btn btn-sm btn-danger">Remove</button></td>
</tr>
</table>
</div>
</div>
<script>
$(function(){
$(".code").change(function(){
var code = $(this).val();
$.ajax({
url : 'select.php',
method : 'POST',
dataType : 'JSON',
data : {code:code},
success : function(echo){
$('.name').val(echo.name);
$('.price').val(echo.price);
}
});
});
})
This is My vue Code:
const app = new Vue({
el: '#app',
data: {
cats: [],
newCat: {mtype:'',name:'',rules:''}
},
mounted() {
if (localStorage.getItem('cats')) {
try {
this.cats = JSON.parse(localStorage.getItem('cats'));
}catch(e){
localStorage.removeItem('cats');
}
}
},
methods: {
addCat() {
// ensure they actually typed something
if (!this.newCat) {
return;
}
this.cats.push(this.newCat);
this.newCat = {mtype:'',name:'',rules:''};
this.saveCats();
},
removeCat(x) {
this.cats.splice(x, 1);
this.saveCats();
},
saveCats() {
const parsed = JSON.stringify(this.cats);
localStorage.setItem('cats', parsed);
}
}
})
Here is Screenshot
When I Select Drop down Option
When I Select Drop down Option
When Click Add Button
But When I select Drop Down and manually type other field
Then the result

angularjs data is not showing in table

I am creating a web app in which I am using angularjs for SQL connectivity.
I have two users (admin,Regional Partner Manager) data of admin is showing properly on my table but data of Regional Partner manager Is Not showing.
Here is my js file:
$scope.getsonvindata = function () {
$scope.loadimage = true;
$scope.names = '';
$scope.resetfilters();
//$scope.area = location;
// get sonvin data list and stored in sonvinrpm $scope variable
$http.get('/angularwebservice/frmcosonvinrpm4.asmx/frmsonvinrpm', {
params: {
log: log,
from: $scope.from,
to: $scope.to,
pm: pm
}
})
.then(function (response) {
$scope.sonvinrpm = response.data.procompanysonVin;
console.log($scope.sonvinrpm);
//pagination
$scope.totalItems = $scope.sonvinrpm.length;
$scope.numPerPage = 50000;
$scope.paginate = function (value) {
var begin, end, index;
begin = ($scope.currentPage - 1) * $scope.numPerPage;
end = begin + $scope.numPerPage;
index = $scope.sonvinrpm.indexOf(value);
return (begin <= index && index < end);
};
$scope.loadimage = false;
if ($scope.sonvinrpm == '') {
$scope.errormessage = 'Data Not Found... Please Select Correct Date Range';
}
else {
$scope.errormessage = '';
}
});
My table:
<table id="table" class="table table-bordered font" style="width: 110%;">
<thead>
<tr class="bg-primary">
<th>Edit</th>
<th>SrNo</th>
<th>Date</th>
<th>Hotel/School</th>
<th>Venue</th>
<th>Zone</th>
<th>Location</th>
<th>Start Time</th>
<th>Brand Name</th>
<th>Program</th>
<%--<th>Count</th>--%>
</tr>
</thead>
<tbody>
<tr class="bg-primary">
<td><i class="glyphicon glyphicon-filter"></i></td>
<td></td>
<%--<th>SonvinId</th>--%>
<td>
<div class="left-margin form-control-wrapper form-group has-feedback has-feedback">
<input type="text" class="date floating-label erp-input" ng-model="search.date" placeholder="Date">
</div>
</td>
<td>
<input type="text" ng-model="search.venuename" placeholder="Hotel/School" class="erp-input" /></td>
<td>
<input type="text" ng-model="search.venue" placeholder="Venue" class="erp-input" />
</td>
<%--<th>Day</th>--%>
<%--<th>Company</th>--%>
<td>
<input type="text" ng-model="search.zone" placeholder="Zone" class="erp-input" /></td>
<td>
<input type="text" ng-model="search.location" placeholder="Location" class="erp-input"></td>
<td>
<%--<div class="form-control-wrapper"> id="time"--%>
<input type="text" ng-model="search.starttime" class="floating-label erp-input" placeholder="Start Time">
<%--</div>--%>
</td>
<%-- <th>End</th>--%>
<%--<th>Hrs</th>--%>
<td>
<input type="text" ng-model="search.brand" placeholder="Brand Name" class="erp-input" /></td>
<td>
<input type="text" ng-model="search.program" placeholder="Program" class="erp-input" /></td>
</tr>
<%--| filter :{date: mddate,brand: mdbrand, zone: mdzone, location: mdlocation, starttime: mdstart,program: mdprogram,venuename: mdvenuename,venue: mdvenue }--%>
<tr ng-repeat="x in sonvinrpm | orderBy:predicate:reverse | filter:paginate| filter:search">
<td><button type="button" ng-click="getassigntrainerdata(x)" class="btn btn-sm btn-primary" data-controls-modal="modal-from-dom" data-backdrop="static" data-keyboard="true" data-toggle="modal" data-target="#assigntrainermodel"><i class="glyphicon glyphicon-pencil"></i></button></td>
<td>{{x.srno}}</td>
<%--<td>{{x.sonvinid}}</td>--%>
<td>{{x.date}}</td>
<td class="bg-success"><a ng-bind="x.venuename" ng-click="DetailsFunction(x)" class="erp-table-a" data-controls-modal="modal-from-dom" data-backdrop="static" data-keyboard="true" data-toggle="modal" data-target="#Detailsmodel"></a></td>
<td>{{x.venue}}</td>
<%-- <td>{{x.day}}</td>--%>
<%--<td>{{x.company}}</td>--%>
<td>{{x.zone}}</td>
<td>{{x.location}}</td>
<td>{{x.starttime}}</td>
<%--<td>{{x.endtime}}</td>--%>
<%--<td>{{x.hrs}}</td>--%>
<td>{{x.brand}}</td>
<td>{{x.program}}</td>
<%--<td>count</td>--%>
</tr>
<tr>
<td colspan="12"><pagination total-items="totalItems" ng-model="currentPage" max-size="5" boundary-links="true" items-per-page="numPerPage" class="pagination-sm"></pagination></td>
</tr>
</tbody>
</table>
and now the data I am getting from web service for my Regional Partner Manager:
{"procompanysonVin":[{"srno":1,"sonvinid":null,"id":3401,"date":"22-10-2016","day":"Sat ","company":24,"brand":"QED - TM","zone":"East","location":"Kolkata ","starttime":"10:00","endtime":"12:00","hrs":"02:00:00 ","program":"HBKBH","venuename":" NARAYANA SCHOOL","venue":" SITLA ASANSOL"},{"srno":2,"sonvinid":null,"id":3400,"date":"23-10-2016","day":"Sun ","company":24,"brand":"QED - TM","zone":"East","location":"Kolkata ","starttime":"10:00","endtime":"12:00","hrs":"02:00:00 ","program":"HBKBH","venuename":"NARAYANA SCHOOL","venue":" BENGAL AMBUJA HOUSING COMPLEX AMBUJA DURGAPUR WEST BENGAL"}]}
the data is coming in my webservice, what is wrong here?
NOTE: data of admin is printing on my table but not of regional partner manager
First thing is i was not able to find where are you calling $scope.getsonvindata function. You are just defining function but not calling anywhere in the code. like this
$scope.getsonvindata();
Second is you have applied filters wrongly.
Here is the proper way to set order by filter.
add scope
$scope.predicate = 'venue';
$scope.reverse = true;
then add to the ng-repeat
orderBy:predicate:reverse
Here is the working link
https://jsfiddle.net/U3pVM/27907/
In the .then(function () You have set
$scope.sonvinrpm = response.data.procompanysonVin;
But while setting data in the table you have set data model as search.zone, search.location, search.date etc.
Firstly you need to correct the key for data access.
Secondly the data which you are getting from the http request is an object which contains array.
So you need to use ng-repeat (or any other iterating logic) to traverse the data for displaying in the table.
Thirdly, sonvinrpm is an object, So you need to do null or undefined check
if ($scope.sonvinrpm == '') {
$scope.errormessage = 'Data Not Found... Please Select Correct Date Range';
}
should be replaced with
if (!$scope.sonvinrpm) {
$scope.errormessage = 'Data Not Found... Please Select Correct Date Range';
}

Is it possible to dynamically assign `ng-model` in AngularJS

Problem Description
I am trying to create a simple Grid using AngularJS. Each cell in this grid has one text-input . There is one extra text-input(I call it global) whose ng-model should be dynamincally assigned to one of the grid-cell, whenever the user focus on that grid-cell.
Isn't that clear ?? Let me show my unsuccessful implementation :
The Markup
<body ng-controller="MainCtrl">
<b> Global : </b>
<input type="text", ng-model="global" size=50 />
<br />
<b> Grid : </b>
<table>
<thead>
<tr>
<th>Name</th>
<th>Address</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in items">
<td> <input type="text" ng-model="item.name" grid-input="global" /></td>
<td> <input type="text" ng-model="item.address" grid-input="global" /></td>
<td> <input type="text" ng-model="item.email" grid-input="global" /></td>
</tr>
</tbody>
</table>
</body>
The Angular App
var app = angular.module('app', []);
app.directive('gridInput', function($rootScope){
return {
restrict : 'AE'
, scope : {
model : '=ngModel'
, global : '=gridInput'
}
, link : function(scope, iElem, iAttrs) {
$(iElem).on('focus', function(e){
scope.global = scope.model;//what is this doing?? I don't KNOW!
})
}
}
});
app.controller('MainCtrl', function($scope) {
$scope.items = [
{name : 'Lekhnath Rijal', address:'Ilam, Nepal', email:'me#gmail.com'},
{name : 'abc def', address:'ghi, jkl', email:'mnop#qrst.uv'}
];
});
What do I want
I want two-way data binding between the global text-input and one of the grid-cell after the cell gets focused. The binding between these two inputs should persist until one of other grid-cell receives focus.
Here is a Plunker
Instead of using custom-directive, you can use ng-change, ng-focus to change the selected item.
index.html
<body ng-controller="MainCtrl">
<b> Global : </b>
<input type="text", ng-model="global.text" size=50 />
<br />
<b> Grid : </b>
<table>
<thead>
<tr>
<th>Name</th>
<th>Address</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in items">
<td>
<input
type="text"
ng-model="item.name"
ng-focus="global.text=item.name; setSelectedItem(item, 'name')"
ng-change="global.text=item.name"/>
</td>
<td>
<input
type="text"
ng-model="item.address"
ng-focus="global.text=item.address; setSelectedItem(item, 'address')"
ng-change="global.text=item.address"/>
</td>
<td>
<input
type="text"
ng-model="item.email"
ng-focus="global.text=item.email; setSelectedItem(item, 'email')" ng-change="global.text=item.email"/>
</td>
</tr>
</tbody>
</table>
</body>
app.js
app.controller('MainCtrl', function($scope) {
$scope.global = {};
$scope.items = [{
name: 'Lekhnath Rijal',
address: 'Ilam, Nepal',
email: 'me#gmail.com'
}, {
name: 'abc def',
address: 'ghi, jkl',
email: 'mnop#qrst.uv'
}];
$scope.$watch('global.text', function(text) {
if (text != undefined && $scope.selectedItem) {
$scope.selectedItem[$scope.selectedAttribute] = text;
}
}); $scope.setSelectedItem = function(item, attribute) {
$scope.selectedItem = item;
$scope.selectedAttribute = attribute;
}
});
Here is the plunker:
http://plnkr.co/edit/r7rIiT?p=preview

Categories