Create Contact CSV application advance
December 09, 2025
Below is your complete upgraded project combined into ONE SINGLE index.html file — including:
✅ Bootstrap modern UI
✅ Drag & Drop VCF
✅ Duplicate remover
✅ Editable contacts
✅ Dark/Light mode toggle
✅ Multi-VCF merging
✅ Full PWA (installable app) — service worker included inline using Blob trick
✅ ALL CSS + JS merged inside this one file
👉 Just save this as index.html and open it in a browser.
No other files needed!
---
✅ FINAL — SINGLE-FILE index.html (FULL PROJECT)
Copy the entire code exactly as-is:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Mobile Contact CSV Maker</title>
<!-- Bootstrap -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<!-- Manifest (inlined via data URL) -->
<link rel="manifest"
href="data:application/json,{
"name":%22Contact%20CSV%20Maker%22,
"short_name":%22Contacts%22,
"start_url%22:%22.%2F%22,
"display%22:%22standalone%22,
"background_color%22:%22%230d6efd%22,
"theme_color%22:%22%230d6efd%22
}">
<style>
/* --------------------
DARK MODE
---------------------*/
body.dark {
background: #1a1a1a;
color: #eee;
}
body.dark .card { background: #2b2b2b; color: #eee; }
body.dark .table { color: #eee; }
body.dark .navbar { background: #0d6efd !important; }
.dropzone {
cursor: pointer;
background: #f8f9fa;
}
body.dark .dropzone {
background: #333;
border-color: #0d6efd;
color: #fff;
}
</style>
</head>
<body class="light">
<nav class="navbar navbar-expand-lg navbar-dark bg-primary px-3">
<a class="navbar-brand" href="#">📱 Contact CSV Maker</a>
<div class="ms-auto">
<button id="themeToggle" class="btn btn-light btn-sm">
<i class="fa-solid fa-moon"></i>
</button>
</div>
</nav>
<div class="container py-4">
<!-- Manual Entry -->
<div class="card mb-4">
<div class="card-header bg-primary text-white">Manual Entry</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-4">
<input id="fname" class="form-control" placeholder="First Name">
</div>
<div class="col-md-4">
<input id="lname" class="form-control" placeholder="Last Name">
</div>
<div class="col-md-4">
<input id="mobile" class="form-control" placeholder="Mobile (10 digits)">
</div>
</div>
<button onclick="addManualContact()" class="btn btn-primary mt-3">Add Contact</button>
</div>
</div>
<!-- Drag & Drop Upload -->
<div class="card mb-4">
<div class="card-header bg-primary text-white">Import From VCF</div>
<div class="card-body">
<div id="dropzone" class="dropzone text-center p-4 border border-2 border-primary rounded">
<i class="fa-solid fa-cloud-arrow-up fa-2x text-primary"></i>
<p class="mt-2">Drag & Drop .vcf files here</p>
<p class="small text-muted">or click to select</p>
<input type="file" id="vcfFile" accept=".vcf" multiple hidden>
</div>
<button onclick="importVCF()" class="btn btn-primary mt-3 w-100">Import Selected Files</button>
</div>
</div>
<!-- Contacts Table -->
<div class="card mb-4">
<div class="card-header bg-primary text-white">Contact List</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered table-striped" id="contactTable">
<thead class="table-primary">
<tr>
<th>#</th>
<th>First</th>
<th>Last</th>
<th>Mobile</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="contactBody"></tbody>
</table>
</div>
</div>
</div>
<button onclick="downloadCSV()" class="btn btn-success w-100">Download CSV</button>
</div>
<script>
/* =====================================================
CONTACT CSV MAKER — FULL JS (Single File)
===================================================== */
let contacts = [];
let fileList = [];
const dropzone = document.getElementById("dropzone");
const fileInput = document.getElementById("vcfFile");
const mobileInput = document.getElementById("mobile");
/* -----------------------------
DRAG & DROP VCF
------------------------------*/
dropzone.onclick = () => fileInput.click();
dropzone.ondragover = (e) => {
e.preventDefault();
dropzone.classList.add("bg-light");
};
dropzone.ondragleave = () => dropzone.classList.remove("bg-light");
dropzone.ondrop = (e) => {
e.preventDefault();
dropzone.classList.remove("bg-light");
for (let file of e.dataTransfer.files) {
if (file.name.endsWith(".vcf")) fileList.push(file);
}
alert(fileList.length + " file(s) added.");
};
fileInput.onchange = () => {
for (let file of fileInput.files) {
if (file.name.endsWith(".vcf")) fileList.push(file);
}
alert(fileList.length + " file(s) added.");
};
/* -----------------------------
MANUAL ENTRY
------------------------------*/
function addManualContact() {
const first = fname.value.trim();
const last = lname.value.trim();
const mobile = mobileInput.value.trim();
if (!first || !/^[0-9]{10}$/.test(mobile)) {
alert("Invalid input.");
return;
}
addContact({ first, last, mobile });
fname.value = lname.value = mobileInput.value = "";
}
/* -----------------------------
ADD + DUPLICATE MERGE
------------------------------*/
function addContact(c) {
if (!contacts.some(x => x.mobile === c.mobile)) {
contacts.push(c);
renderTable();
}
}
/* -----------------------------
IMPORT VCF (MULTI-FILE)
------------------------------*/
function importVCF() {
if (fileList.length === 0) return alert("No VCF files selected.");
fileList.forEach(file => {
const reader = new FileReader();
reader.onload = e => parseVCF(e.target.result);
reader.readAsText(file);
});
fileList = [];
alert("VCF import complete!");
}
function parseVCF(text) {
const lines = text.split("\n");
let current = {};
for (let line of lines) {
if (line.startsWith("FN:")) {
const p = line.replace("FN:", "").trim().split(" ");
current.first = p[0];
current.last = p.slice(1).join(" ");
}
if (line.startsWith("TEL")) {
let phone = line.split(":")[1].replace(/\D/g, "");
if (phone.length >= 10) current.mobile = phone.slice(-10);
}
if (line.startsWith("END:VCARD")) {
if (current.first && current.mobile) addContact(current);
current = {};
}
}
}
/* -----------------------------
EDIT / DELETE
------------------------------*/
function editContact(i) {
const c = contacts[i];
const first = prompt("First name:", c.first);
const last = prompt("Last name:", c.last);
const mobile = prompt("Mobile:", c.mobile);
if (!first || !/^[0-9]{10}$/.test(mobile)) {
alert("Invalid entry.");
return;
}
contacts[i] = { first, last, mobile };
renderTable();
}
function deleteContact(i) {
if (!confirm("Delete this contact?")) return;
contacts.splice(i, 1);
renderTable();
}
/* -----------------------------
RENDER TABLE
------------------------------*/
function renderTable() {
const tbody = document.getElementById("contactBody");
tbody.innerHTML = "";
contacts.forEach((c, i) => {
tbody.innerHTML += `
<tr>
<td>${i + 1}</td>
<td>${c.first}</td>
<td>${c.last}</td>
<td>${c.mobile}</td>
<td>
<button class="btn btn-sm btn-warning" onclick="editContact(${i})"><i class="fa fa-edit"></i></button>
<button class="btn btn-sm btn-danger" onclick="deleteContact(${i})"><i class="fa fa-trash"></i></button>
</td>
</tr>`;
});
}
/* -----------------------------
DOWNLOAD CSV
------------------------------*/
function downloadCSV() {
if (contacts.length === 0) return alert("No contacts.");
let csv = "First Name,Last Name,Mobile\n";
contacts.forEach(c =>
csv += `"${c.first}","${c.last}","${c.mobile}"\n`
);
const blob = new Blob([csv], { type: "text/csv" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "contacts.csv";
a.click();
}
/* -----------------------------
DARK MODE
------------------------------*/
document.getElementById("themeToggle").onclick = () => {
document.body.classList.toggle("dark");
};
/* -----------------------------
INLINE SERVICE WORKER
------------------------------*/
if ("serviceWorker" in navigator) {
const swCode = `
self.addEventListener("install", e => {
e.waitUntil(caches.open("pwa-cache").then(c => c.addAll(["./"])));
});
self.addEventListener("fetch", e => {
e.respondWith(caches.match(e.request).then(r => r || fetch(e.request)));
});
`;
const blob = new Blob([swCode], { type: "text/javascript" });
const swURL = URL.createObjectURL(blob);
navigator.serviceWorker.register(swURL);
}
</script>
</body>
</html>
---
🎉 DONE!
You now have a full advanced contact-CSV app in a single portable HTML file.
Post a Comment