* 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
搜尋
Patient Care Assistant (Clinical Assistant) - (REF. NO. : NTE2204059)
Admin
2022年4月28日
讀畢需時 1 分鐘
~最新職位詳情~
Patient Care Assistant (Clinical Assistant)-(REF. NO. : NTE2204059)
Rank : Patient Care Assistant IIWork Location : New Territories East ClusterPay : HK$17,888 - 22,357 per month (Please see Remuneration)Responsibilities :
Perform the following core duties for patients.(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.
Submit application on-line on or before 6 May 2022
留言