Pretraga u klijentu
Dodaj iznad tabele polje „Pretraga“ koje pri svakom unosu poziva GET /api/proizvodi/pretraga?q=… (lekcija 4) i prikazuje samo pronađene proizvode; kada je polje prazno, prikazuju se svi.
API vraća samo podatke; korisniku treba stranica. Pravimo je u HTML-u, Bootstrap-u i JavaScript-u: tabela proizvoda, forma za dodavanje i izmenu i dugmad za brisanje – sve preko zahteva GET, POST, PUT i DELETE ka API-ju iz prethodnih lekcija.
| Način | Kako | CORS |
|---|---|---|
| u istoj aplikaciji (ova lekcija) | fajlovi u wwwroot, u Program.cs UseDefaultFiles + UseStaticFiles (lekcija 4) | nije potreban |
| posebna aplikacija na drugom portu ili domenu | npr. Live Server na localhost:5500 | server mora da dozvoli taj izvor (ispod) |
| mobilna ili desktop aplikacija | isti HTTP zahtevi | ne odnosi se na njih (CORS je pravilo pregledača) |
<!DOCTYPE html>
<html lang="sr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Prodavnica – proizvodi</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container py-4">
<h1 class="h3">Proizvodi</h1>
<form id="forma" class="row g-2 align-items-end mb-3">
<input type="hidden" id="id">
<div class="col-12 col-md-5"><label class="form-label" for="naziv">Naziv</label>
<input class="form-control" id="naziv" required maxlength="50"></div>
<div class="col-6 col-md-2"><label class="form-label" for="cena">Cena (din)</label>
<input class="form-control" id="cena" type="number" min="0" required></div>
<div class="col-6 col-md-2"><label class="form-label" for="kolicina">Količina</label>
<input class="form-control" id="kolicina" type="number" min="0" required></div>
<div class="col-12 col-md-3 d-flex gap-2">
<button class="btn btn-primary flex-fill" id="sacuvaj">Dodaj</button>
<button class="btn btn-outline-secondary" type="button" id="odustani" hidden>Odustani</button>
</div>
</form>
<p id="poruka" class="small"></p>
<table class="table table-striped align-middle">
<thead><tr><th>Naziv</th><th class="text-end">Cena</th><th class="text-end">Količina</th><th></th></tr></thead>
<tbody id="tabela"></tbody>
</table>
</div>
<script src="js/proizvodi.js"></script>
</body>
</html>
Sačuvaj kao wwwroot/index.html. Skripta je u posebnom fajlu wwwroot/js/proizvodi.js.
// wwwroot/js/proizvodi.js – klijent za Web API (GET, POST, PUT, DELETE)
const API = "/api/proizvodi"; // ista aplikacija → relativna adresa, bez CORS-a
const $ = id => document.getElementById(id);
function poruka(tekst, greska = false) {
$("poruka").textContent = tekst;
$("poruka").className = "small " + (greska ? "text-danger" : "text-success");
}
// READ: učitaj i prikaži sve proizvode
async function ucitaj() {
const odgovor = await fetch(API);
const proizvodi = await odgovor.json();
const tbody = $("tabela");
tbody.replaceChildren();
for (const p of proizvodi) {
const red = document.createElement("tr");
for (const [tekst, desno] of [[p.naziv, false], [p.cena + " din", true], [p.kolicina, true]]) {
const td = document.createElement("td");
td.textContent = tekst; // textContent – bezbedno
if (desno) td.className = "text-end";
red.append(td);
}
const akcije = document.createElement("td");
akcije.className = "text-end text-nowrap";
const izmeni = document.createElement("button");
izmeni.className = "btn btn-sm btn-outline-primary me-1";
izmeni.textContent = "Izmeni";
izmeni.addEventListener("click", () => zaIzmenu(p));
const obrisi = document.createElement("button");
obrisi.className = "btn btn-sm btn-outline-danger";
obrisi.textContent = "Obriši";
obrisi.addEventListener("click", () => obrisiProizvod(p));
akcije.append(izmeni, obrisi);
red.append(akcije);
tbody.append(red);
}
}
function zaIzmenu(p) { // popuni formu podacima proizvoda
$("id").value = p.id;
$("naziv").value = p.naziv;
$("cena").value = p.cena;
$("kolicina").value = p.kolicina;
$("sacuvaj").textContent = "Sačuvaj izmene";
$("odustani").hidden = false;
}
function ocistiFormu() {
$("forma").reset();
$("id").value = "";
$("sacuvaj").textContent = "Dodaj";
$("odustani").hidden = true;
}
// CREATE ili UPDATE
$("forma").addEventListener("submit", async e => {
e.preventDefault();
const id = $("id").value;
const podaci = { naziv: $("naziv").value.trim(), cena: Number($("cena").value), kolicina: Number($("kolicina").value) };
const odgovor = await fetch(id ? `${API}/${id}` : API, {
method: id ? "PUT" : "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(podaci)
});
if (odgovor.ok) {
poruka(id ? "Proizvod je izmenjen." : "Proizvod je dodat.");
ocistiFormu();
ucitaj();
} else {
const greska = await odgovor.json().catch(() => null); // 400: detalji provere sa servera
const opis = greska && greska.errors ? Object.values(greska.errors).flat().join(" ") : odgovor.status;
poruka("Greška: " + opis, true);
}
});
$("odustani").addEventListener("click", ocistiFormu);
// DELETE
async function obrisiProizvod(p) {
if (!confirm(`Obrisati „${p.naziv}“?`)) return;
const odgovor = await fetch(`${API}/${p.id}`, { method: "DELETE" });
poruka(odgovor.ok ? "Proizvod je obrisan." : "Brisanje nije uspelo.", !odgovor.ok);
ucitaj();
}
ucitaj();
| Operacija | Zahtev | Šta radi stranica posle |
|---|---|---|
| Read | fetch(API) | pravi redove tabele (textContent – bezbedno) |
| Create | POST sa JSON telom | poruka, prazna forma, ponovo učitava listu |
| Update | PUT /api/proizvodi/{id} | isto; forma je bila popunjena klikom na „Izmeni“ |
| Delete | DELETE /api/proizvodi/{id} | pita za potvrdu, pa ponovo učitava listu |
Ako server odbije podatke (npr. negativna cena), odgovor 400 sadrži polje errors sa porukama iz atributa modela – stranica ih prikazuje. Proveru u pregledaču (required, min) korisnik može zaobići, ali proveru na serveru ne može.
// Program.cs – dozvoli zahteve samo sa adrese klijenta
builder.Services.AddCors(opcije => opcije.AddPolicy("klijent", politika =>
politika.WithOrigins("http://localhost:5500") // adresa stranice klijenta
.AllowAnyHeader()
.AllowAnyMethod()));
var app = builder.Build();
app.UseCors("klijent"); // pre MapControllers
app.MapControllers();
Pregledač ne dozvoljava stranici sa jedne adrese da čita odgovore sa druge, osim ako server to izričito dozvoli (zaglavlje Access-Control-Allow-Origin). Ne koristi AllowAnyOrigin() za API koji menja podatke. U klijentu tada adresa API-ja mora biti puna: const API = "http://localhost:5123/api/proizvodi".
Startup, SQL Server i Dapper). Ideje su iste, ali je kod na ovoj strani pisan za današnji .NET (sve u Program.cs) – kada se razlikuju, važi kod sa strane.Za svaki zadatak postoji primer, uputstvo i rešenje.
Dodaj iznad tabele polje „Pretraga“ koje pri svakom unosu poziva GET /api/proizvodi/pretraga?q=… (lekcija 4) i prikazuje samo pronađene proizvode; kada je polje prazno, prikazuju se svi.
wwwroot.Content-Type: application/json, inače server vraća 415.1. Kojim zahtevom klijent menja postojeći proizvod?
2. Kada je potreban CORS?
3. Zašto se tekst iz baze upisuje sa textContent?
video 1 |
video 2 |
video 3 |
video 4 |
$(document).ready(function () {
onStartLoad();
});
let table;
async function onStartLoad() {
//call getData function
getData()
.then((data) => {
loadDataList(data);
})
.catch((error) => {
alert("Unable to get items." + error);
});
const idFeature=3;
getFeatureData(idFeature)
.then((data) => {
prikazFeatureData(data);
})
.catch((error) => {
alert("Unable to get items." + error);
}); //log the data
}
async function getData() {
//await the response of the fetch call/*Poziv GET metode u web API aplikaciji*/
let response = await fetch("https://localhost:5001/products");
//proceed once the first promise is resolved.
let data = await response.json();
//proceed only when the second promise is resolved
return data;
}
async function loadDataList(data) {
let product_list = document.querySelector("#productList");
table = document.createElement("table");
table.classList.add("productsTable");
product_list.appendChild(table);
let i = 0;
let row;
for (key in data) {
var obj = data[key];
if (obj instanceof Object) {
if (i % 4 == 0) {
row = document.createElement("TR");
row.classList.add("d-flex");
row.classList.add("flex-row");
row.classList.add("justify-content-around");
//It has 4 div elements in a row
table.appendChild(row); //Add row in the table
}
let h1Title = document.createElement("h2");
let priceParagraph = document.createElement("p");
let colorParagraph = document.createElement("p");
let sizeParagraph = document.createElement("p");
let quantityParagraph = document.createElement("p");
let btnDiv=document.createElement("div");
let aEdit=document.createElement("a");
let aDelete=document.createElement("a");
btnDiv.classList.add("d-flex");
aEdit.classList.add("btn");
aEdit.classList.add("btn-success");
aEdit.classList.add("text-white");
aEdit.setAttribute('href','edit.html?id='+obj.productId);
aEdit.style.width = '100%';
aEdit.innerText = 'Edit';
aDelete.classList.add("btn");
aDelete.classList.add("btn-danger");
aDelete.classList.add("text-white");
aDelete.style.width = '100%';
aDelete.style.cursor = 'pointer';
aDelete.innerText = 'Delete';
const urlDel='https://localhost:5001/products/Delete/'+obj.productId;
aDelete.onclick= function(){
Delete(urlDel);
};
h1Title.innerHTML = h1Title.innerHTML + obj.name;
priceParagraph.innerHTML =
priceParagraph.innerHTML + "Price: " + obj.price + " $";
colorParagraph.innerHTML =
colorParagraph.innerHTML + "Color: " + obj.color;
sizeParagraph.innerHTML = sizeParagraph.innerHTML + "Size: " + obj.size;
quantityParagraph.innerHTML =
quantityParagraph.innerHTML + "Quantity: " + obj.quantity;
let column = document.createElement("TD");
column.appendChild(h1Title);
column.appendChild(priceParagraph);
column.appendChild(colorParagraph);
column.appendChild(sizeParagraph);
column.appendChild(quantityParagraph);
column.appendChild(btnDiv);/*new*/
btnDiv.appendChild(aEdit);/*new*/
btnDiv.appendChild(aDelete);/*new*/
row.appendChild(column);
i++;
}
}
}
async function getFeatureData(id) {
//await the response of the fetch call/*Poziv GET metode unutar web API aplikacije */
let response = await fetch("https://localhost:5001/products/"+id);
//proceed once the first promise is resolved.
let data = await response.json();
//proceed only when the second promise is resolved
return data;
}
async function prikazFeatureData(data) {
/*var dataArr = data["data"]; varijanta kad se spakuju podaci u JSON */
let nameOfArticleFeature = document.querySelector("#prod1");
let priceOfArticleFeature = document.querySelector("#prodPrice");
nameOfArticleFeature.innerHTML = data.name;
priceOfArticleFeature.innerHTML = data.price + " $";
}
/*New*/
async function Delete(url) {
await swal({
title: "Are you sure?",
text: "You will not be able to recover this imaginary file!",
icon: "warning",
buttons: true,
dangerMode: true,
}).then((willDelete) => {
if (willDelete) {
$.ajax({
url: url,
dataSrc: function (data) {
var json = JSON.parse(JSON.stringify(data));
return json;
},
type: "DELETE",
datatype: "json",
success: function (data) {
if (data.success) {
toastr.success(data.success);
window.location.reload();
} else {
toastr.err(data.message);
}
},
});
} else {
}
});
}
| WEB APLICATION | WEB API | ||
|---|---|---|---|
| description | ajax call | called method | returning value |
| Zahtev za listu proizvoda |
//await the response of the fetch call |
GetAllProducts() |
Task < ActionResult < IEnumerable < Product > > > |
| Zahtev za proizvod sa određenim ID-om |
"
//await the response of the fetch call |
GetProduct(int id) |
Task < ActionResult < Product > >
|
| Unošenje novog proizvoda u bazu podataka |
var uriCreate = uri + "/Create"; |
Post(Product product) |
Task < ActionResult < int > > |
| Ažuriranje određenog proizvoda |
var uriUpdate = uri + "/Update"; |
PutAsync(int id,Product product)
|
Task < ActionResult < int > >
|
| Brisanje određenog proizvoda |
const urlForDelete="..."; |
DeleteProduct(int id)
|
Task< ActionResult < int > >
|