<!DOCTYPE html>
<html lang="en" ng-app="jsonToCsvApp">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AngularJS JSON to CSV Exporter</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
<script type="module" src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
<script nomodule src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #0d1b2a 0%, #1b263b 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 40px;
}
.header h1 {
font-size: 36px;
margin-bottom: 10px;
font-weight: 600;
}
.header p {
font-size: 16px;
opacity: 0.9;
}
.content-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
.card {
background: white;
border-radius: 16px;
padding: 30px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
}
.card h2 {
font-size: 22px;
color: #0d1b2a;
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
}
.json-input {
width: 100%;
min-height: 300px;
padding: 15px;
border: 2px solid #e0e0e0;
border-radius: 10px;
font-family: 'Courier New', monospace;
font-size: 13px;
resize: vertical;
transition: border-color 0.3s;
line-height: 1.6;
}
.json-input:focus {
outline: none;
border-color: #0d1b2a;
}
.sample-buttons {
display: flex;
gap: 10px;
margin-top: 15px;
flex-wrap: wrap;
}
.btn-sample {
padding: 8px 16px;
border: 2px solid #e0e0e0;
border-radius: 8px;
background: white;
cursor: pointer;
font-size: 13px;
font-weight: 600;
color: #666;
transition: all 0.3s ease;
}
.btn-sample:hover {
border-color: #0d1b2a;
color: #0d1b2a;
}
.options-section {
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #e0e0e0;
}
.options-section h3 {
font-size: 16px;
color: #333;
margin-bottom: 15px;
}
.option-group {
margin-bottom: 15px;
}
.checkbox-label {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
font-size: 14px;
color: #666;
}
.checkbox-label input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
accent-color: #0d1b2a;
}
.input-group {
margin-bottom: 15px;
}
.input-group label {
display: block;
font-size: 13px;
font-weight: 600;
color: #666;
margin-bottom: 5px;
}
.input-group input {
width: 100%;
padding: 10px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
}
.input-group input:focus {
outline: none;
border-color: #0d1b2a;
}
.preview-table {
width: 100%;
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
th {
background: #0d1b2a;
color: white;
padding: 12px;
text-align: left;
font-weight: 600;
}
td {
padding: 10px 12px;
border-bottom: 1px solid #e0e0e0;
}
tr:hover {
background: #f8f9fa;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: #999;
}
.empty-state ion-icon {
font-size: 80px;
margin-bottom: 15px;
opacity: 0.3;
}
.error-message {
background: #fee;
color: #c33;
padding: 15px;
border-radius: 8px;
margin-top: 15px;
font-size: 14px;
display: flex;
align-items: center;
gap: 10px;
}
.success-message {
background: #d4edda;
color: #155724;
padding: 15px;
border-radius: 8px;
margin-top: 15px;
font-size: 14px;
display: flex;
align-items: center;
gap: 10px;
}
.btn {
padding: 14px 24px;
border: none;
border-radius: 10px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 10px;
width: 100%;
justify-content: center;
}
.btn-primary {
background: linear-gradient(135deg, #0d1b2a 0%, #1b263b 100%);
color: white;
margin-bottom: 10px;
}
.btn-primary:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(13, 27, 42, 0.4);
}
.btn-primary:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-secondary {
background: #10b981;
color: white;
}
.btn-secondary:hover {
background: #059669;
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(16, 185, 129, 0.4);
}
.stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin-top: 20px;
}
.stat-card {
background: #f8f9fa;
padding: 15px;
border-radius: 10px;
text-align: center;
}
.stat-value {
font-size: 28px;
font-weight: 700;
color: #0d1b2a;
}
.stat-label {
font-size: 12px;
color: #666;
margin-top: 5px;
}
.info-box {
background: #fff9e6;
border-left: 4px solid #fbbf24;
padding: 15px;
border-radius: 8px;
margin-top: 20px;
font-size: 13px;
color: #666;
}
.info-box strong {
color: #92400e;
}
@media (max-width: 1024px) {
.content-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
body {
padding: 10px;
}
.header h1 {
font-size: 28px;
}
.card {
padding: 20px;
}
.stats {
grid-template-columns: 1fr;
}
.sample-buttons {
flex-direction: column;
}
.btn-sample {
width: 100%;
}
}
</style>
</head>
<body ng-controller="JsonToCsvController">
<div class="container">
<div class="header">
<h1>JSON to CSV Exporter</h1>
<p>Convert JSON data to CSV format with AngularJS</p>
</div>
<div class="content-grid">
<!-- Left Panel: JSON Input -->
<div class="card">
<h2>
<ion-icon name="code-outline"></ion-icon>
JSON Input
</h2>
<textarea class="json-input"
ng-model="jsonInput"
placeholder="Paste your JSON data here..."
ng-change="parseJSON()"></textarea>
<div class="sample-buttons">
<button class="btn-sample" ng-click="loadSample('users')">
<ion-icon name="people-outline"></ion-icon>
Sample: Users
</button>
<button class="btn-sample" ng-click="loadSample('products')">
<ion-icon name="cart-outline"></ion-icon>
Sample: Products
</button>
<button class="btn-sample" ng-click="loadSample('orders')">
<ion-icon name="receipt-outline"></ion-icon>
Sample: Orders
</button>
</div>
<div class="options-section">
<h3>Export Options</h3>
<div class="option-group">
<label class="checkbox-label">
<input type="checkbox" ng-model="options.includeHeaders" ng-change="updatePreview()">
<span>Include column headers</span>
</label>
</div>
<div class="option-group">
<label class="checkbox-label">
<input type="checkbox" ng-model="options.flattenNested" ng-change="updatePreview()">
<span>Flatten nested objects</span>
</label>
</div>
<div class="input-group">
<label>Delimiter</label>
<input type="text" ng-model="options.delimiter" ng-change="updatePreview()" maxlength="1">
</div>
<div class="input-group">
<label>Filename</label>
<input type="text" ng-model="options.filename">
</div>
</div>
<div ng-show="errorMessage" class="error-message">
<ion-icon name="alert-circle-outline"></ion-icon>
{{errorMessage}}
</div>
<div ng-show="successMessage" class="success-message">
<ion-icon name="checkmark-circle-outline"></ion-icon>
{{successMessage}}
</div>
</div>
<!-- Right Panel: Preview & Export -->
<div class="card">
<h2>
<ion-icon name="eye-outline"></ion-icon>
CSV Preview
</h2>
<div class="preview-table" ng-show="parsedData.length > 0">
<table>
<thead>
<tr>
<th ng-repeat="header in headers">{{header}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in parsedData | limitTo:10">
<td ng-repeat="header in headers">{{row[header]}}</td>
</tr>
</tbody>
</table>
<p style="text-align: center; color: #999; font-size: 12px; margin-top: 10px;" ng-show="parsedData.length > 10">
Showing first 10 of {{parsedData.length}} rows
</p>
</div>
<div class="empty-state" ng-show="parsedData.length === 0 && !errorMessage">
<ion-icon name="document-outline"></ion-icon>
<p>Enter JSON data to see preview</p>
</div>
<button class="btn btn-primary"
ng-click="convertToCSV()"
ng-disabled="parsedData.length === 0">
<ion-icon name="sync-outline"></ion-icon>
Convert to CSV
</button>
<button class="btn btn-secondary"
ng-click="downloadCSV()"
ng-disabled="!csvData">
<ion-icon name="download-outline"></ion-icon>
Download CSV File
</button>
<div class="stats">
<div class="stat-card">
<div class="stat-value">{{parsedData.length}}</div>
<div class="stat-label">Rows</div>
</div>
<div class="stat-card">
<div class="stat-value">{{headers.length}}</div>
<div class="stat-label">Columns</div>
</div>
<div class="stat-card">
<div class="stat-value">{{csvSize}}</div>
<div class="stat-label">Size (KB)</div>
</div>
</div>
<div class="info-box">
<strong>Tip:</strong> You can paste JSON arrays directly, or use one of the sample datasets to test the converter. The preview shows the first 10 rows.
</div>
</div>
</div>
</div>
<script>
angular.module('jsonToCsvApp', [])
.controller('JsonToCsvController', function($scope) {
$scope.jsonInput = '';
$scope.parsedData = [];
$scope.headers = [];
$scope.csvData = null;
$scope.errorMessage = '';
$scope.successMessage = '';
$scope.csvSize = 0;
$scope.options = {
includeHeaders: true,
flattenNested: false,
delimiter: ',',
filename: 'export.csv'
};
$scope.samples = {
users: [
{ id: 1, name: 'John Doe', email: 'john@example.com', age: 30, city: 'New York' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', age: 25, city: 'Los Angeles' },
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com', age: 35, city: 'Chicago' },
{ id: 4, name: 'Alice Williams', email: 'alice@example.com', age: 28, city: 'Houston' },
{ id: 5, name: 'Charlie Brown', email: 'charlie@example.com', age: 32, city: 'Phoenix' }
],
products: [
{ id: 101, name: 'Laptop', category: 'Electronics', price: 999.99, stock: 50 },
{ id: 102, name: 'Smartphone', category: 'Electronics', price: 699.99, stock: 100 },
{ id: 103, name: 'Headphones', category: 'Electronics', price: 199.99, stock: 200 },
{ id: 104, name: 'Desk Chair', category: 'Furniture', price: 299.99, stock: 30 },
{ id: 105, name: 'Monitor', category: 'Electronics', price: 399.99, stock: 75 }
],
orders: [
{ orderId: 'ORD-001', customer: 'John Doe', total: 1299.98, status: 'Shipped', date: '2024-01-15' },
{ orderId: 'ORD-002', customer: 'Jane Smith', total: 899.99, status: 'Processing', date: '2024-01-16' },
{ orderId: 'ORD-003', customer: 'Bob Johnson', total: 499.99, status: 'Delivered', date: '2024-01-14' },
{ orderId: 'ORD-004', customer: 'Alice Williams', total: 1599.99, status: 'Shipped', date: '2024-01-17' },
{ orderId: 'ORD-005', customer: 'Charlie Brown', total: 299.99, status: 'Processing', date: '2024-01-18' }
]
};
$scope.loadSample = function(type) {
$scope.jsonInput = JSON.stringify($scope.samples[type], null, 2);
$scope.parseJSON();
};
$scope.parseJSON = function() {
$scope.errorMessage = '';
$scope.successMessage = '';
if (!$scope.jsonInput.trim()) {
$scope.parsedData = [];
$scope.headers = [];
return;
}
try {
var data = JSON.parse($scope.jsonInput);
if (!Array.isArray(data)) {
data = [data];
}
if ($scope.options.flattenNested) {
data = data.map(function(item) {
return flattenObject(item);
});
}
$scope.parsedData = data;
$scope.extractHeaders();
$scope.successMessage = 'JSON parsed successfully!';
} catch (e) {
$scope.errorMessage = 'Invalid JSON: ' + e.message;
$scope.parsedData = [];
$scope.headers = [];
}
};
function flattenObject(obj, prefix = '') {
var flattened = {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
var newKey = prefix ? prefix + '.' + key : key;
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
Object.assign(flattened, flattenObject(obj[key], newKey));
} else {
flattened[newKey] = obj[key];
}
}
}
return flattened;
}
$scope.extractHeaders = function() {
if ($scope.parsedData.length === 0) {
$scope.headers = [];
return;
}
var headersSet = {};
$scope.parsedData.forEach(function(item) {
Object.keys(item).forEach(function(key) {
headersSet[key] = true;
});
});
$scope.headers = Object.keys(headersSet);
};
$scope.updatePreview = function() {
if ($scope.parsedData.length > 0) {
$scope.parseJSON();
}
};
$scope.convertToCSV = function() {
if ($scope.parsedData.length === 0) return;
var csv = [];
var delimiter = $scope.options.delimiter || ',';
// Add headers
if ($scope.options.includeHeaders) {
csv.push($scope.headers.map(escapeCSVValue).join(delimiter));
}
// Add data rows
$scope.parsedData.forEach(function(row) {
var values = $scope.headers.map(function(header) {
var value = row[header];
return escapeCSVValue(value);
});
csv.push(values.join(delimiter));
});
$scope.csvData = csv.join('\n');
$scope.csvSize = (new Blob([$scope.csvData]).size / 1024).toFixed(2);
$scope.successMessage = 'CSV generated successfully!';
};
function escapeCSVValue(value) {
if (value === null || value === undefined) {
return '';
}
var stringValue = String(value);
if (stringValue.indexOf(',') !== -1 ||
stringValue.indexOf('"') !== -1 ||
stringValue.indexOf('\n') !== -1) {
stringValue = '"' + stringValue.replace(/"/g, '""') + '"';
}
return stringValue;
}
$scope.downloadCSV = function() {
if (!$scope.csvData) {
$scope.convertToCSV();
}
var blob = new Blob([$scope.csvData], { type: 'text/csv;charset=utf-8;' });
var link = document.createElement('a');
var url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', $scope.options.filename);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
$scope.successMessage = 'CSV file downloaded!';
};
});
</script>
</body>
</html>
No comments yet. Be the first!