* HKILD Section Engagement Tracking for Wix
*
* Tracks:
* - Time spent in each section (using Intersection Observer)
* - Button clicks in each section
*
* Sends data to Wix Collection: hkild_section_engagement
*
* Installation:
* 1. Add this code to Wix Custom Code element
* 2. Place on PD mobile page
*/
(function() {
'use strict';
// Configuration
const config = {
collectionId: 'section_engagement',
sections: [
{ id: 'hero', name: 'Hero + Stats', selector: '.hero, [class*="hero"]' },
{ id: 'honest-check', name: '報讀前問自己', selector: '[class*="honest"]' },
{ id: 'four-advantages', name: '四大核心優勢', selector: '[class*="advantage"]' },
{ id: 'course-content', name: '理論實戰並重', selector: '[class*="course"], [class*="content"]' },
{ id: 'live-practice', name: '實戰練習', selector: '[class*="practice"], [class*="live"]' },
{ id: 'graduation', name: '畢業成果', selector: '[class*="graduate"], [class*="graduation"]' },
{ id: 'outcomes', name: '課程成果', selector: '[class*="outcome"]' },
{ id: 'testimonials', name: '學員真實分享', selector: '[class*="testimonial"]' },
{ id: 'schedule', name: '開班時間', selector: '[class*="schedule"], [class*="time"]' },
{ id: 'faq', name: '常見問題', selector: '[class*="faq"], [class*="question"]' }
],
flushInterval: 30000 // Send data every 30 seconds
};
// Track state
const state = {
sections: {},
pendingData: [],
flushTimer: null
};
// Initialize section tracking state
config.sections.forEach(section => {
state.sections[section.id] = {
name: section.name,
timeSpent: 0,
entered: 0,
left: 0,
lastEntered: null,
isCurrentlyIn: false
};
});
/**
* Format date as YYYY-MM-DD
*/
function getDateString() {
const now = new Date();
return now.toISOString().split('T')[0];
}
/**
* Get ISO timestamp
*/
function getTimestamp() {
return new Date().toISOString();
}
/**
* Send data to Wix Collection
*/
async function sendDataToWix(data) {
try {
// Use Wix Data API to insert item
const response = await fetch(`https://www.wixapis.com/v1/contacts/me`, {
method: 'GET',
headers: {
'Authorization': wixLocation.authorization
}
});
if (response.ok) {
// If we can authenticate, send data
const insertResponse = await fetch(
`https://www.wixapis.com/wix-data/v2/items/${config.collectionId}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': wixLocation.authorization
},
body: JSON.stringify({
dataItem: {
data: {
section_id: data.sectionId,
page_url: window.location.href,
time_spent: data.timeSpent,
button_clicks: data.buttonClicks || 0,
last_updated: new Date().toISOString().split('T')[0],
visitor_id: data.visitorId || 'anonymous'
}
}
})
}
);
if (insertResponse.ok) {
console.log('✅ Tracking data sent to Wix:', data);
} else {
console.warn('⚠️ Failed to send tracking data to Wix:', insertResponse.status);
}
}
} catch (error) {
console.warn('⚠️ Could not send to Wix API:', error.message);
// Fallback: store in localStorage as backup
saveToLocalStorage(data);
}
}
/**
* Fallback: save to localStorage
*/
function saveToLocalStorage(data) {
try {
const key = 'hkild_user_behavior';
const existing = JSON.parse(localStorage.getItem(key) || '{}');
const today = getDateString();
if (!existing[today]) {
existing[today] = { sections: {}, buttonClicks: [] };
}
// Update section data
existing[today].sections[data.sectionId] = {
timeSpent: data.timeSpent,
entered: data.entered,
left: data.left
};
localStorage.setItem(key, JSON.stringify(existing));
} catch (e) {
console.warn('localStorage not available:', e);
}
}
/**
* Flush pending data to Wix
*/
async function flushData() {
if (state.pendingData.length === 0) return;
const dataToSend = [...state.pendingData];
state.pendingData = [];
for (const data of dataToSend) {
await sendDataToWix(data);
// Small delay between requests
await new Promise(resolve => setTimeout(resolve, 100));
}
}
/**
* Record section entry
*/
function recordSectionEntry(sectionId) {
if (!state.sections[sectionId]) return;
const section = state.sections[sectionId];
section.entered++;
section.lastEntered = Date.now();
section.isCurrentlyIn = true;
console.log(`📍 Entered: ${section.name}`);
}
/**
* Record section exit
*/
function recordSectionExit(sectionId) {
if (!state.sections[sectionId]) return;
const section = state.sections[sectionId];
if (section.lastEntered) {
const elapsed = Math.round((Date.now() - section.lastEntered) / 1000);
section.timeSpent += elapsed;
section.left++;
console.log(`📍 Exited: ${section.name} (${elapsed}s)`);
}
section.isCurrentlyIn = false;
// Queue data to send
const dataToSend = {
date: getDateString(),
sectionId: sectionId,
sectionName: section.name,
timeSpent: section.timeSpent,
entered: section.entered,
left: section.left,
timestamp: getTimestamp()
};
state.pendingData.push(dataToSend);
}
/**
* Record button click in section
*/
function recordButtonClick(sectionId, buttonText) {
const dataToSend = {
date: getDateString(),
sectionId: sectionId,
sectionName: state.sections[sectionId]?.name || 'Unknown',
buttonText: buttonText,
timestamp: getTimestamp()
};
state.pendingData.push(dataToSend);
console.log(`🔘 Button clicked in ${sectionId}: ${buttonText}`);
}
/**
* Setup Intersection Observer for section tracking
*/
function setupIntersectionObserver() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
recordSectionEntry(entry.target.dataset.sectionId);
} else {
recordSectionExit(entry.target.dataset.sectionId);
}
});
}, {
threshold: 0.1 // Trigger when 10% of section is visible
});
// Observe all sections
config.sections.forEach(section => {
const elements = document.querySelectorAll(section.selector);
elements.forEach(el => {
el.dataset.sectionId = section.id;
observer.observe(el);
});
});
console.log('✅ Intersection Observer initialized');
}
/**
* Setup button click tracking
*/
function setupButtonTracking() {
document.addEventListener('click', (event) => {
const button = event.target.closest('button, a[role="button"]');
if (!button) return;
// Find which section this button is in
let section = button.closest('[data-section-id]');
if (section) {
const sectionId = section.dataset.sectionId;
const buttonText = button.textContent.trim().substring(0, 50);
recordButtonClick(sectionId, buttonText);
}
}, true);
console.log('✅ Button tracking initialized');
}
/**
* Initialize everything
*/
function initialize() {
console.log('🚀 HKILD Tracking Initialized');
// Setup observers
setupIntersectionObserver();
setupButtonTracking();
// Auto-flush data periodically
state.flushTimer = setInterval(flushData, config.flushInterval);
// Flush on page unload
window.addEventListener('beforeunload', flushData);
// Expose for debugging
window.hkildTracking = {
getState: () => state,
flushData: flushData,
recordButtonClick: recordButtonClick,
recordSectionEntry: recordSectionEntry,
recordSectionExit: recordSectionExit
};
}
// Start when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize);
} else {
initialize();
}
})();
top of page
搜尋
~HA最新職位~Patient Care Assistant /ClinicalAssistant (New Territories East Cluster)(REF.NO.NTE2407053)
Admin
2024年8月2日
讀畢需時 1 分鐘
~HA職位詳情~
Patient Care Assistant (Clinical Assistant) - (REF. NO.: NTE2407053)
Pay:HK$21,739 - 24,701 per month (Up to 5% of total basic salary as end-of-contract gratuity may be offered to contract staff upon completion of the contract subject to satisfactory performance.)
Key Responsibilities :
(a) blood-taking and related duties which include taking blood specimen and preparing blood specimen labels/blood-taking materials, blood culture, blood taking for Pediatric patients, blood glucose analysis using blood glucose meter etc.
(b) ECG for patients and related duties.
(c) Intravenous (IV) cannulation insertion and assessing the appropriate IV site.
(d) taking care and monitoring patients condition while performing the blood-taking, ECG or IV process and reporting to senior in case of abnormal situations.
留言