mvpweb/formfinder-scraper-wizard
0
1document.addEventListener('DOMContentLoaded', function() {2 const scrapeBtn = document.getElementById('scrape-btn');3 const exportBtn = document.getElementById('export-btn');4 const resultsContainer = document.getElementById('results-container');5 const resultsBody = document.getElementById('results-body');6 const loadingIndicator = document.getElementById('loading');7// Initialize mock data array8let mockResults = [];9 10// Generate mock data for 100 sites when needed11function generateMockData() {12 mockResults = Array.from({length: 100}, (_, i) => {13const mockResults = Array.from({length: 100}, (_, i) => {14 const hasForm = Math.random() > 0.3; // 70% chance of having a form15 const cities = ['miami', 'orlando', 'tampa', 'jacksonville', 'fortlauderdale', 'pensacola', 'sarasota', 'naples', 'daytona', 'gainesville'];16 const dealerships = ['autos', 'motors', 'cars', 'dealers', 'auto', 'usedcars', 'preowned', 'luxury', 'trucks', 'imports'];17 const tlds = ['com', 'org', 'net', 'io', 'co'];18 const paths = ['contact', 'contact-us', 'get-in-touch', 'support', 'help'];19 20 const city = cities[Math.floor(Math.random() * cities.length)];21 const dealer = dealerships[Math.floor(Math.random() * dealerships.length)];22 const tld = tlds[Math.floor(Math.random() * tlds.length)];23 const path = paths[Math.floor(Math.random() * paths.length)];24 const url = `https://${city}${dealer}.${tld}/${path}`;25 // Add some "used car dealers in florida" specific keywords to some URLs26 if (i % 3 === 0) {27 const floridaKeywords = ['used-car-dealers-florida', 'florida-used-cars', 'best-used-cars-florida', 'used-auto-florida'];28 const keyword = floridaKeywords[Math.floor(Math.random() * floridaKeywords.length)];29 url = `https://${keyword.replace(/-/g, '')}.${tld}/${path}`;30 }31 if (!hasForm) {32 return { url, hasForm: false };33 }34const methods = ['POST', 'GET'];35 const actions = ['/submit', '/contact', '/process', '/send', '/form-handler'];36 const fieldTypes = ['text', 'email', 'tel', 'textarea', 'select'];37 const fieldLabels = ['Name', 'Email', 'Phone', 'Message', 'Subject', 'Company', 'Inquiry'];38 39 const numFields = Math.floor(Math.random() * 5) + 3; // 3-7 fields40 const fields = Array.from({length: numFields}, (_, i) => {41 const type = fieldTypes[Math.floor(Math.random() * fieldTypes.length)];42 const label = fieldLabels[Math.floor(Math.random() * fieldLabels.length)];43 return {44 name: label.toLowerCase().replace(' ', '_') + (i > 0 ? i : ''),45 type,46 label47 };48 });49 50 return {51 url,52 hasForm: true,53 formDetails: {54 method: methods[Math.floor(Math.random() * methods.length)],55 action: actions[Math.floor(Math.random() * actions.length)],56 fields57 }58 };59 });60 61 // Pagination variables62 let currentPage = 1;63 const resultsPerPage = 10;64 // Start scraping button click handler65 scrapeBtn.addEventListener('click', async function() {66 const keywords = document.getElementById('keywords').value.trim();67 const maxResults = document.getElementById('max-results').value;68 69 if (!keywords) {70 alert('Please enter at least one keyword');71 return;72 }73 74 // Show loading indicator75 loadingIndicator.classList.remove('hidden');76 resultsContainer.classList.add('hidden');77 scrapeBtn.disabled = true;78 scrapeBtn.classList.add('opacity-75');79 80 try {81 // Using ScraperAPI as an example (you would need to sign up for an API key)82 const apiKey = 'YOUR_SCRAPERAPI_KEY'; // Replace with your actual API key83 const searchQuery = encodeURIComponent(keywords);84 const apiUrl = `https://api.scraperapi.com/structured/google/search?api_key=${apiKey}&q=${searchQuery}&num=${maxResults}`;85 86 const response = await fetch(apiUrl);87 const data = await response.json();88 89 // Process results into our format90 const processedResults = data.organic_results.map(result => ({91 url: result.link,92 hasForm: Math.random() > 0.3, // Randomly assign form presence for demo93 formDetails: {94 method: 'POST',95 action: '/submit',96 fields: [97 { name: 'name', type: 'text', label: 'Name' },98 { name: 'email', type: 'email', label: 'Email' },99 { name: 'message', type: 'textarea', label: 'Message' }100 ]101 }102 }));103 104 currentPage = 1;105 displayResults(processedResults);106 } catch (error) {107 console.error('Scraping failed:', error);108 alert('Scraping failed. Please try again later.');109 // Fall back to mock data if API fails110 generateMockData();111 currentPage = 1;112 displayResults(mockResults);113} finally {114 loadingIndicator.classList.add('hidden');115 resultsContainer.classList.remove('hidden');116 scrapeBtn.disabled = false;117 scrapeBtn.classList.remove('opacity-75');118 }119});120 // Export button click handler121 exportBtn.addEventListener('click', function() {122 // Convert results to CSV123 const rows = mockResults.map(result => {124 return [125 `"${result.url}"`,126 result.hasForm ? 'Yes' : 'No',127 result.hasForm ? result.formDetails.method : '',128 result.hasForm ? result.formDetails.action : '',129 result.hasForm ? result.formDetails.fields.length : ''130 ].join(',');131 });132 133 const csvContent = [134 ['URL', 'Has Form', 'Method', 'Action', 'Field Count'].join(','),135 ...rows136 ].join('\n');137 138 // Create download link139 const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });140 const url = URL.createObjectURL(blob);141 const link = document.createElement('a');142 link.setAttribute('href', url);143 link.setAttribute('download', 'formfinder_results.csv');144 link.style.visibility = 'hidden';145 document.body.appendChild(link);146 link.click();147 document.body.removeChild(link);148});149 // Display results in the table with pagination150 function displayResults(results) {151 resultsBody.innerHTML = '';152 153 // Calculate pagination154 const startIndex = (currentPage - 1) * resultsPerPage;155 const endIndex = Math.min(startIndex + resultsPerPage, results.length);156 const paginatedResults = results.slice(startIndex, endIndex);157 158 // Display current page results159 paginatedResults.forEach((result, index) => {160const row = document.createElement('tr');161 row.className = 'result-row';162 row.style.animationDelay = `${index * 0.1}s`;163 164 row.innerHTML = `165 <td class="px-6 py-4 whitespace-nowrap">166 <div class="text-sm font-medium text-gray-900 truncate max-w-xs">${result.url}</div>167 </td>168 <td class="px-6 py-4 whitespace-nowrap">169 <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full 170 ${result.hasForm ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}">171 ${result.hasForm ? 'Yes' : 'No'}172 </span>173 </td>174 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">175 <button class="view-form-btn text-blue-600 hover:text-blue-900 mr-3" data-url="${result.url}">176 <i data-feather="eye" class="w-4 h-4"></i>177 </button>178<button class="text-blue-600 hover:text-blue-900">179 <i data-feather="external-link" class="w-4 h-4"></i>180 </button>181 </td>182 `;183 resultsBody.appendChild(row);184 });185 186 // Add pagination controls187 const totalPages = Math.ceil(results.length / resultsPerPage);188 const paginationContainer = document.createElement('div');189 paginationContainer.className = 'flex justify-between items-center mt-4';190 191 paginationContainer.innerHTML = `192 <div class="text-sm text-gray-500">193 Showing ${startIndex + 1}-${endIndex} of ${results.length} results194 </div>195 <div class="flex space-x-2">196 <button id="prev-page" class="px-3 py-1 border rounded ${currentPage === 1 ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-50'}">197 Previous198 </button>199 <div class="flex items-center">200 Page ${currentPage} of ${totalPages}201 </div>202 <button id="next-page" class="px-3 py-1 border rounded ${currentPage === totalPages ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-50'}">203 Next204 </button>205 </div>206 `;207 208 resultsContainer.appendChild(paginationContainer);209 feather.replace();210 211 // Pagination event listeners212 document.getElementById('prev-page')?.addEventListener('click', () => {213 if (currentPage > 1) {214 currentPage--;215 displayResults(results);216 window.scrollTo({ top: 0, behavior: 'smooth' });217 }218 });219 220 document.getElementById('next-page')?.addEventListener('click', () => {221 if (currentPage < totalPages) {222 currentPage++;223 displayResults(results);224 window.scrollTo({ top: 0, behavior: 'smooth' });225 }226 });227// Add click handlers for view form buttons228 document.querySelectorAll('.view-form-btn').forEach(btn => {229 btn.addEventListener('click', function() {230 const url = this.getAttribute('data-url');231 const result = mockResults.find(r => r.url === url);232 if (result && result.hasForm) {233 showFormDetails(result.formDetails, url);234 }235 });236 });237 }238 239 // Show form details in modal240 function showFormDetails(formDetails, url) {241 const modal = document.createElement('div');242 modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';243 modal.innerHTML = `244 <div class="bg-white rounded-lg shadow-xl max-w-2xl w-full max-h-[80vh] overflow-y-auto">245 <div class="p-6">246 <div class="flex justify-between items-start mb-4">247 <h3 class="text-lg font-medium text-gray-900">Form Details: ${url}</h3>248 <button class="close-modal text-gray-400 hover:text-gray-500">249 <i data-feather="x"></i>250 </button>251 </div>252 <div class="space-y-4">253 <div>254 <h4 class="text-sm font-medium text-gray-700 mb-2">Form Attributes</h4>255 <div class="grid grid-cols-2 gap-4">256 <div>257 <p class="text-sm text-gray-500">Method</p>258 <p class="text-sm font-medium text-gray-900">${formDetails.method}</p>259 </div>260 <div>261 <p class="text-sm text-gray-500">Action</p>262 <p class="text-sm font-medium text-gray-900">${formDetails.action}</p>263 </div>264 </div>265 </div>266 <div>267 <h4 class="text-sm font-medium text-gray-700 mb-2">Form Fields</h4>268 <div class="border border-gray-200 rounded-lg overflow-hidden">269 <table class="min-w-full divide-y divide-gray-200">270 <thead class="bg-gray-50">271 <tr>272 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>273 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th>274 <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Label</th>275 </tr>276 </thead>277 <tbody class="bg-white divide-y divide-gray-200">278 ${formDetails.fields.map(field => `279 <tr>280 <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${field.name}</td>281 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${field.type}</td>282 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${field.label}</td>283 </tr>284 `).join('')}285 </tbody>286 </table>287 </div>288 </div>289 </div>290 </div>291 </div>292 `;293 294 document.body.appendChild(modal);295 feather.replace();296 297 // Close modal handler298 modal.querySelector('.close-modal').addEventListener('click', () => {299 modal.remove();300 });301 302 // Close when clicking outside303 modal.addEventListener('click', (e) => {304 if (e.target === modal) {305 modal.remove();306 }307 });308 }309// Add pulse animation to scrape button on hover310 scrapeBtn.addEventListener('mouseenter', function() {311 this.classList.add('pulse');312 });313 314 scrapeBtn.addEventListener('mouseleave', function() {315 this.classList.remove('pulse');316 });317});