F7 List Item Navigation
August 7, 2025 Β· AI Conversations
—
### β Example Setup
#### 1. **Define a route in `f7-init.js`**
var f7App = new Framework7({
el: '#app',
name: 'MyApp',
theme: 'auto',
routes: [
{
path: '/event-details/',
url: 'pages/event-details.html', // This is your HTML page
},
],
});
#### 2. **List Item Click to Open Page**
In your page HTML (e.g., `home.html`):
<div class="list">
<ul>
<li>
<a href="/event-details/" class="item-link item-content">
<div class="item-inner">
<div class="item-title">View Event</div>
</div>
</a>
</li>
</ul>
</div>
> β This will open `event-details.html` as a **new page**, not as a modal.
—
### π§ Notes:
– Make sure your “ is present and wraps your pages.
– All pages should be inside `.views` and `.view-main` to work with F7 router transitions.
– Donβt use jQuery `.on('click')` unless you prevent default and manually navigate using `f7App.views.main.router.navigate()`.
—
### Optional: JavaScript navigation
If you want to open the page via a jQuery click handler:
$(document).on('click', '.open-event-page', function () {
f7App.views.main.router.navigate('/event-details/');
});
HTML:
<li><a href="#" class="item-link item-content open-event-page">
<div class="item-inner">
<div class="item-title">View Event</div>
</div>
</a></li>
Let me know if you want to pass dynamic data or parameters to the new page.
test
Sunday at 29/06/25 –
05:00 PM
0/500
like this I want also display data in detail page
—
## β Step-by-Step Implementation
### πΉ 1. Define a route for the event detail page
In your `routes` config:
routes: [
{
path: '/event/:id/:dateId/',
componentUrl: 'pages/event-details.html',
},
],
—
### πΉ 2. HTML for the list item (as-is, add class and data)
Add a class like `.open-event` to your `.event-item`:
<div class="space-y-4 mb-4 event-item list-group-item active open-event"
data-event-id="381"
data-event-date-id="2319"
data-title="test"
data-time="Sunday at 29/06/25 - 05:00 PM"
data-capacity="0/500">
<div class="rounded-lg border border-border/40 card bg-background hover:shadow-lg transition-shadow cursor-pointer">
<div class="p-6">
<div class="flex justify-between items-center">
<div>
<h3 class="text-xl font-semibold">test</h3>
<p class="text-gray-600">Sunday at 29/06/25 - 05:00 PM</p>
</div>
<div class="text-right">
<div class="inline-flex items-center rounded-full border font-semibold transition-all px-2.5 py-0.5 text-xs">
0/500
</div>
</div>
</div>
</div>
</div>
</div>
—
### πΉ 3. JavaScript to handle the click and navigate
$(document).on('click', '.open-event', function () {
const eventId = $(this).data('event-id');
const dateId = $(this).data('event-date-id');
const title = $(this).data('title');
const time = $(this).data('time');
const capacity = $(this).data('capacity');
// Pass data via route props
f7.views.main.router.navigate(`/event/${eventId}/${dateId}/`, {
props: {
title,
time,
capacity,
},
});
});
—
### πΉ 4. Event Details Page (event-details.html)
<template>
<div class="page">
<div class="navbar">
<div class="navbar-inner sliding">
<div class="title">Event Details</div>
</div>
</div>
<div class="page-content">
<div class="block">
<h2 class="text-2xl font-bold">{{title}}</h2>
<p class="text-gray-600">{{time}}</p>
<p class="mt-2 text-sm">Capacity: {{capacity}}</p>
</div>
</div>
</div>
</template>
<script>
export default (props, { $f7 }) => {
return {
data() {
return {
title: props.title,
time: props.time,
capacity: props.capacity,
};
},
};
};
</script>
—
## π§ Summary
– Your `.event-item` contains the data in `data-*` attributes.
– On click, you grab the data and route to `/event/:id/:dateId/`.
– Framework7 passes the props to the detail page.
– Detail page shows the dynamic content.
Let me know if you want to also fetch full event data from API using the ID.
—
## β Step-by-Step with `href`
### πΉ 1. Update your `routes` in `f7-init.js`
routes: [
{
path: '/event/:id/:dateId/',
componentUrl: 'pages/event-details.html',
},
],
—
### πΉ 2. Update your List Item HTML with `href`
You can wrap your `.event-item` with an “ tag like this:
<a href="/event/381/2319/?title=test&time=Sunday%20at%2029/06/25%20-%2005:00%20PM&capacity=0/500"
class="event-link">
<div class="space-y-4 mb-4 list-group-item active">
<div class="rounded-lg border border-border/40 card bg-background hover:shadow-lg transition-shadow cursor-pointer">
<div class="p-6">
<div class="flex justify-between items-center">
<div>
<h3 class="text-xl font-semibold">test</h3>
<p class="text-gray-600">Sunday at 29/06/25 - 05:00 PM</p>
</div>
<div class="text-right">
<div class="inline-flex items-center rounded-full border font-semibold transition-all px-2.5 py-0.5 text-xs">
0/500
</div>
</div>
</div>
</div>
</div>
</div>
</a>
> β This will navigate like a normal link using Framework7's router. Make sure itβs inside a `.view`.
—
### πΉ 3. `event-details.html` to Read URL and Query Params
<template>
<div class="page">
<div class="navbar">
<div class="navbar-inner sliding">
<div class="title">Event Details</div>
</div>
</div>
<div class="page-content">
<div class="block">
<h2 class="text-2xl font-bold">{{title}}</h2>
<p class="text-gray-600">{{time}}</p>
<p class="mt-2 text-sm">Capacity: {{capacity}}</p>
</div>
</div>
</div>
</template>
<script>
export default (props, { $f7route }) => {
return {
data() {
return {
title: $f7route.query.title,
time: $f7route.query.time,
capacity: $f7route.query.capacity,
};
},
};
};
</script>
—
### π§ Tip for Dynamic Data
If you want to fetch full event info based only on `event-id`, then just pass that in `href="/event/381/2319/"`, and in the `event-details.html`, fetch details using Ajax.
—
Would you like that version too β fetching event details from API based on the ID only?
at n.append (dom7.esm.js:831:17)
at Ta (parse-component.js:79:13)
at Object.parse (component.js:29:16)
at component-loader.js:140:51
append @ dom7.esm.js:831
Ta @ parse-component.js:79
parse @ component.js:29
(anonymous) @ component-loader.js:140
Promise.then
componentLoader @ component-loader.js:139
pageComponentLoader @ component-loader.js:186
Ot @ navigate.js:739
(anonymous) @ navigate.js:871
h @ navigate.js:868
(anonymous) @ navigate.js:987
n @ process-route-queue.js:62
Tt @ process-route-queue.js:84
Lt.navigate @ navigate.js:970
(anonymous) @ index.js:235
dispatch @ jquery-3.4.1.min.js:2
v.handle @ jquery-3.4.1.min.js:2Understand this error
parse-component.js:81 Uncaught (in promise) TypeError: t[n] is not a function
at Ta (parse-component.js:81:28)
at Object.parse (component.js:29:16)
at component-loader.js:140:51
Uncaught SyntaxError: Failed to execute 'appendChild' on 'Node': Unexpected token 'export'
Because **Framework7 is trying to parse your `event-details.html` as a component**, but it contains **raw ES module `export` syntax**, which is **not valid in `.html` files** unless youβre using Webpack or a build system (like Framework7 with Vue/React CLI builds).
—
## β Quick Fix: Use `page` HTML without `export default`
If you're using `componentUrl: 'event-details.html'`, your `event-details.html` **must NOT** contain “ with `export default`.
Instead, use Framework7's **Template7-style** or **plain HTML page structure**.
—
### β Replace your `event-details.html` with this:
<div class="page" data-name="event-details">
<div class="navbar">
<div class="navbar-inner sliding">
<div class="title">Event Details</div>
</div>
</div>
<div class="page-content">
<div class="block">
<h2 class="event-title text-2xl font-bold"></h2>
<p class="event-time text-gray-600"></p>
<p class="event-capacity mt-2 text-sm"></p>
</div>
</div>
</div>
<script>
// Page init hook
document.addEventListener('page:init', function (e) {
const page = e.detail;
// Get query params
const title = page.route.query.title || 'No Title';
const time = page.route.query.time || '';
const capacity = page.route.query.capacity || '';
page.$el.find('.event-title').text(title);
page.$el.find('.event-time').text(time);
page.$el.find('.event-capacity').text('Capacity: ' + capacity);
});
</script>
—
## β Summary
| Problem | Solution |
|——–|———-|
| You used `export default` in `.html` | That only works in `.js`/`.vue` files in Vue CLI/Webpack builds |
| You're using `.html` with `componentUrl` | Use plain Framework7 page format with `page:init` script |
| You want to pass data | Use query params in `href`, and read them in `page.route.query` |
—
Let me know if you want to use `.vue` single-file components instead β that needs a build setup with Framework7 CLI.
FOSSETT'S CIRCUS
Customer system
Scan Customer Ticket
Scan QR Code
{{title}}-{{location}}
{{time}}
No scans yet
Set Code Manually
<script>
// Page init hook
document.addEventListener('page:init', function (e) {
const page = e.detail;
// Get query params
const title = page.route.query.title || 'No Title';
const time = page.route.query.time || '';
const location = page.route.query.location || '';
page.$el.find('.event-title').text(title);
page.$el.find('.event-time').text(time);
page.$el.find('.event-capacity').text('Capacity: ' + capacity);
});
</script>
adjust in above
Since you're already handling data dynamically using `page:init`, here's how to properly **adjust your page to display the passed event details** (title, location, time, etc.).
—
## β Step-by-Step Fix for Your Page
### πΉ 1. Replace the `{{title}}`, `{{location}}`, `{{time}}` section with identifiable “s:
Replace this:
<div>
{{title}}-{{location}}<br>
{{time}}
</div>
With:
<div class="block text-center text-lg font-medium mt-4">
<span class="event-title"></span> - <span class="event-location"></span><br>
<span class="event-time"></span>
</div>
—
### πΉ 2. Update your “ accordingly:
Hereβs the corrected script, including all variables:
<script>
document.addEventListener('page:init', function (e) {
const page = e.detail;
// Get query parameters
const title = page.route.query.title || 'No Title';
const time = page.route.query.time || '';
const location = page.route.query.location || '';
const code = page.route.query.code || '';
// Update DOM elements
page.$el.find('.event-title').text(title);
page.$el.find('.event-location').text(location);
page.$el.find('.event-time').text(time);
// Optional: Fill the input with pre-filled code (if passed)
page.$el.find('input[name="code"]').val(code);
});
</script>
—
## β Example Href to Navigate Here
You should link to this page using something like:
<a href="/event/381/2319/?title=Test%20Event&location=Main%20Tent&time=29/06/25%20-%2005:00%20PM&code=ABC123">
<div class="event-item">...</div>
</a>
You can dynamically build this `href` using JS/jQuery if needed.
—
## β Summary
– β `{{title}}` syntax won't work in `.html` unless you use a Template7 `.tpl` file or a rendering engine.
– β
Use `.text()` in `page:init` with class-based spans like `.event-title`.
– β
Use query strings in the `href` to pass data dynamically.
Let me know if you'd rather use a `.js` component or fetch the event data from API using just the ID instead.
—
## β Step-by-Step Setup
### β 1. Define route in your Framework7 app:
routes: [
{
path: '/event/:id/:dateId/',
url: 'pages/event-details.html',
},
],
—
### β 2. Update your list item with `href` and query parameters:
Hereβs how you can build your clickable list item using an “:
<a
href="/event/381/2319/?title=Test%20Event&location=Main%20Tent&time=29/06/25%20-%2005:00%20PM&code=ABC123"
class="event-link"
>
<div class="space-y-4 mb-4 list-group-item active">
<div class="rounded-lg border border-border/40 card bg-background hover:shadow-lg transition-shadow cursor-pointer">
<div class="p-6">
<div class="flex justify-between items-center">
<div>
<h3 class="text-xl font-semibold">Test Event</h3>
<p class="text-gray-600">29/06/25 - 05:00 PM</p>
</div>
<div class="text-right">
<div class="inline-flex items-center rounded-full border font-semibold px-2.5 py-0.5 text-xs">
0/500
</div>
</div>
</div>
</div>
</div>
</div>
</a>
Make sure this link is inside the `.view-main` wrapper so Framework7 can intercept and navigate properly.
—
### β 3. Update `event-details.html` to handle those query params:
Ensure this part exists **in `pages/event-details.html`**:
<div class="block text-center text-lg font-medium mt-4">
<span class="event-title"></span> - <span class="event-location"></span><br>
<span class="event-time"></span>
</div>
And include this “ block:
<script>
document.addEventListener('page:init', function (e) {
const page = e.detail;
const title = page.route.query.title || 'No Title';
const location = page.route.query.location || '';
const time = page.route.query.time || '';
const code = page.route.query.code || '';
page.$el.find('.event-title').text(title);
page.$el.find('.event-location').text(location);
page.$el.find('.event-time').text(time);
page.$el.find('input[name="code"]').val(code);
});
</script>
—
## β Final Notes
– The `href` route format:
`/event/{eventId}/{eventDateId}/?title=…&location=…&time=…&code=…`
– Framework7 auto-handles this navigation as long as it's inside `.view-main`.
—
Would you like help dynamically building these `href`s using JavaScript/jQuery from data attributes?
at Ta (parse-component.js:94:13)
at Object.parse (component.js:29:16)
at component-loader.js:140:51
Uncaught (in promise) TypeError: Cannot set properties of undefined (setting 'id')
is coming from **`parse-component.js`**, which is part of Framework7's internal component parsing logic. It typically means:
> β οΈ You're trying to load a `componentUrl` (which expects a Vue/Svelte/React component file), but you passed it an **HTML file** that is not a proper Framework7 component.
—
## β Solution: Use `url` instead of `componentUrl`
Since you're working with a plain `.html` file (`event-details.html`), **you must use `url`**, not `componentUrl`.
### β Correct route definition:
routes: [
{
path: '/event/:id/:dateId/',
url: 'pages/event-details.html', // β
not componentUrl
},
],
### β Don't do this:
routes: [
{
path: '/event/:id/:dateId/',
componentUrl: 'pages/event-details.html', // β wrong for plain HTML
},
],
—
## β Recap of the working setup
1. **Link:**
<a href="/event/381/2319/?title=Test&location=MainTent&time=29/06/25%20-%2005:00%20PM&code=ABC123">Open Event</a>
2. **Route:**
{
path: '/event/:id/:dateId/',
url: 'pages/event-details.html',
}
3. **HTML file (`event-details.html`)** has a `.page` and a `page:init` listener to access query params.
—
## β Final Fix Checklist
– [x] Your route uses `url`, not `componentUrl`
– [x] The linked file is a plain `.html` with `…`
– [x] No `export default` inside `.html`
– [x] Data passed via query string and read via `page.route.query`
Let me know if you want to switch to `.js` or `.vue` component-based routing β that requires a different setup.
FOSSETT'S CIRCUS
Customer system
Scan Customer Ticket
Scan QR Code
No scans yet
Set Code Manually
<script>
document.addEventListener('page:init', function (e) {
const page = e.detail;
const title = page.route.query.title || 'No Title';
const location = page.route.query.location || '';
const time = page.route.query.time || '';
const code = page.route.query.code || '';
console.log(title, location, time, code);
page.$el.find('.event-title').text(title);
page.$el.find('.event-location').text(location);
page.$el.find('.event-time').text(time);
page.$el.find('input[name="code"]').val(code);
});
</script>
not working
—
## β Common Reasons and Fixes
### πΉ 1. β Your `href` may be missing proper URL encoding or structure
Make sure your `href` includes query parameters **after the route**, like this:
<a href="/event/381/2319/?title=Test%20Event&location=Main%20Tent&time=29%2F06%2F25%20-%2005%3A00%20PM&code=ABC123">
Open Event
</a>
You can encode it manually or use `encodeURIComponent()` in JS.
—
### πΉ 2. β You must use `url` not `componentUrl` in your routes
Ensure your route is defined like this:
routes: [
{
path: '/event/:id/:dateId/',
url: 'pages/event-details.html', // β
for static HTML
},
],
Using `componentUrl` here would cause the `parse-component.js` error you're seeing.
—
### πΉ 3. β Page must be wrapped inside `.view-main` and `.views`
Make sure your layout in `index.html` or wherever you define the views looks like:
<div class="views">
<div class="view view-main">
<!-- initial page here -->
</div>
</div>
Framework7βs router only intercepts links inside `.view`.
—
### πΉ 4. β Confirm your `event-details.html` is being loaded
Add a quick check:
document.addEventListener('page:init', function (e) {
const page = e.detail;
if (page.name === 'event-details') {
console.log('Event details page loaded');
const title = page.route.query.title || 'No Title';
const location = page.route.query.location || '';
const time = page.route.query.time || '';
const code = page.route.query.code || '';
page.$el.find('.event-title').text(title);
page.$el.find('.event-location').text(location);
page.$el.find('.event-time').text(time);
page.$el.find('input[name="code"]').val(code);
}
});
If this log never shows up, the page isn't loaded or the route isnβt matching.
—
### πΉ 5. β Framework7 may not parse query strings unless `href` is 100% correct
Even a small typo in the query string or route path can break parsing.
**Working example:**
<a href="/event/123/456/?title=Test%20Event&location=Big%20Top&time=29%2F06%2F25%20-%2005%3A00%20PM&code=XYZ789">
View Event
</a>
—
## β Diagnostic Tip
Open your browser dev tools β Console β and see:
– Does the page route to `event-details.html`?
– Does `console.log(title, location, time, code);` show correct values?
If not, please paste the exact “ you're using, and Iβll validate the URL encoding and structure.
Let me know and Iβll help you solve it in minutes.
{
if(type=='qr')
f7App.dialog.preloader('Scanning code…');
else
f7App.dialog.preloader('Sending code…');
$('.scan').prop('disabled', true);
$('.manually').prop('disabled', true);
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html('Code '+ code +' sent to boxoffice, plase wait…');
$.ajax({
url: `${ApiUrl}scan`,
method: 'POST',
data: {
token: TOKEN,
event_date_id: eventDateId,
order_code: code
},
success: function(result,status,xhr)
{
var apiResult = JSON.parse(result);
if ( apiResult.result )
{
$('#scan-result').removeClass('bg-dark').addClass('bg-success').html(apiResult.message+''+apiResult.seating+''+apiResult.tickets);
f7App.dialog.preloader(`${apiResult.message}`);
// reset input to set next code
$('[name="code"]').val(eventId +'.'+ eventDateId +'.');
}
else
{
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html(apiResult.message);
f7App.dialog.preloader(`${apiResult.message}`);
}
},
error: function(xhr,status,error)
{
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html('Code '+ result.text +' could not be sent to boxoffice, please try again');
f7App.dialog.preloader(`${result.text} could not be sent to boxoffice, please try again`);
},
complete: function()
{
$('.scan').prop('disabled', false);
$('.manually').prop('disabled', false);
f7App.dialog.close();
}
});
}
f7dialog not closed even its applied in complete function
Letβs go through it step-by-step and solve it.
—
## π What's Going Wrong
### π΄ Problem:
You're calling `f7App.dialog.preloader(…)` **again inside `success` and `error`**, which opens another preloader. But then you call `f7App.dialog.close()` only once in `complete`, so:
– **The first preloader is replaced** by the second one.
– The second one never gets closed properly.
—
## β Solution
### β Use `f7App.dialog.preloader()` **only once**, and use `f7App.dialog.close()` in `complete`.
### β Replace `f7App.dialog.preloader(…)` inside `success` and `error` with `f7App.dialog.alert(…)` or `toast`.
—
## β Fixed Version of Your Code:
var sendCode = function(code, type) {
if (type == 'qr')
f7App.dialog.preloader('Scanning code...');
else
f7App.dialog.preloader('Sending code...');
$('.scan').prop('disabled', true);
$('.manually').prop('disabled', true);
$('#scan-result')
.removeClass('bg-success')
.addClass('bg-dark')
.html('Code <u class="bg-primary text-white p-1">' + code + '</u> sent to boxoffice, please wait...');
$.ajax({
url: `${ApiUrl}scan`,
method: 'POST',
data: {
token: TOKEN,
event_date_id: eventDateId,
order_code: code
},
success: function(result, status, xhr) {
var apiResult = JSON.parse(result);
if (apiResult.result) {
$('#scan-result')
.removeClass('bg-dark')
.addClass('bg-success')
.html(apiResult.message + '<br>' + apiResult.seating + '<br>' + apiResult.tickets);
// β
Use alert or toast instead of preloader again
f7App.dialog.alert(apiResult.message, 'Scan Success');
$('[name="code"]').val(eventId + '.' + eventDateId + '.');
} else {
$('#scan-result')
.removeClass('bg-success')
.addClass('bg-dark')
.html(apiResult.message);
f7App.dialog.alert(apiResult.message, 'Scan Failed');
}
},
error: function(xhr, status, error) {
const message = 'Could not send code to boxoffice, please try again';
$('#scan-result')
.removeClass('bg-success')
.addClass('bg-dark')
.html('Code <u class="bg-primary text-white p-1">' + code + '</u> ' + message);
f7App.dialog.alert(message, 'Scan Error');
},
complete: function() {
$('.scan').prop('disabled', false);
$('.manually').prop('disabled', false);
f7App.dialog.close(); // β
Only one preloader to close
}
});
};
—
## β Optional Improvement: Use Toast instead of Alert
If you donβt want popups, replace `.alert()` with:
f7App.toast.create({
text: apiResult.message,
closeTimeout: 3000,
}).open();
—
Let me know if you want this wrapped in a reusable toast/alert handler!
if (cordova.platformId === 'android') {
var permissions = cordova.plugins.permissions;
permissions.checkPermission(permissions.CAMERA, function (status) {
if (!status.hasPermission) {
// Request permission
permissions.requestPermission(
permissions.CAMERA,
function (status) {
if (status.hasPermission) {
navigator.splashscreen.hide(); // Hide splash after permission
initializeApp(); // Proceed with your app
} else {
navigator.app.exitApp(); // Exit if denied
}
},
function () {
navigator.app.exitApp(); // Exit on request error
}
);
} else {
navigator.splashscreen.hide();
initializeApp(); // Permission already granted
}
});
} else {
navigator.splashscreen.hide(); // For browser/iOS fallback
initializeApp();
}
}, false);
function initializeApp() {
window.TOKEN = 'fossettscircustoken';
window.eventId = -1;
window.eventDateId = -1;
window.ApiUrl = 'https://staging.fossettscircus.com/boxoffice/index.php/api/';
var scanConfig = {
preferFrontCamera : false, // iOS and Android
showFlipCameraButton : true, // iOS and Android
showTorchButton : true, // iOS and Android
torchOn: false, // Android, launch with the torch switched on (if available)
saveHistory: false, // Android, save scan history (default false)
prompt : "Place a code inside the scan area", // Android
resultDisplayDuration: 500, // Android, display scanned text for X ms. 0 suppresses it entirely, default 1500
formats : "QR_CODE", // default: all but PDF_417 and RSS_EXPANDED
//orientation : "landscape", // Android only (portrait|landscape), default unset so it rotates with the device
disableAnimations : true, // iOS
disableSuccessBeep: false // iOS and Android
};
$(document).ready(function()
{
$(document).on('page:init',function () {
$(document).on('click', '.logout-staff', function()
{
localStorage.removeItem('staff_token');
localStorage.removeItem('is_staff_loggedin');
f7App.views.main.router.navigate('/');
});
});
$(document).on('page:init','.page[data-name="event-details"]',function (e) {
const page = e.detail;
const title = page.route.query.title || 'No Title';
const location = page.route.query.location || '';
const time = page.route.query.time || '';
const code = page.route.query.code || '';
console.log(title, location, time, code);
page.$el.find('.event-title').text(title);
page.$el.find('.event-location').text(location);
page.$el.find('.event-time').text(time);
page.$el.find('input[name="code"]').val(code);
});
$(document).on('page:init', '.page[data-name="home"]', function () {
});
$(document).on('page:init', '.page[data-name="staff-pay-terminal"]', function () {
$(document).on('input', '#amountInput', function () {
const amount = parseFloat($(this).val());
const btn = $('#chargeBtn');
if (amount > 0) {
btn.removeClass('disabled:opacity-50').addClass('color-green');
btn.text('Charge β¬' + amount.toFixed(2));
} else {
btn.addClass('disabled:opacity-50').removeClass('color-green');
btn.text('Charge β¬0.00');
}
});
})
$(document).on('page:init', '.page[data-name="staff-tickets"]', function () {
loadEvents('yes');
$(document).on('click', '.reload-events-staff', function()
{
loadEvents('yes');
});
});
$(document).on('page:init', '.page[data-name="staff-check-orders"]', function () {
});
$(document).on('page:init', '.page[data-name="staff-dashboard"]', function () {
});
$(document).on('page:init', '.page[data-name="staff-login"]', function () {
if(localStorage.getItem('is_staff_loggedin'))
{
f7App.views.main.router.navigate('/staff/');
}
$('#staff-login-form').on('submit', function (e) {
e.preventDefault();
var email = $('#staff-email').val();
var password = $('#staff-password').val();
f7App.dialog.preloader('Logging in…');
$.ajax({
url: `${ApiUrl}staff_login`,
method: 'POST',
data: {
token: TOKEN,
email: email,
password: password
},
success: function (result, status, xhr) {
var apiResult = JSON.parse(result);
window.localStorage.setItem('staff_token', apiResult.token);
window.localStorage.setItem('is_staff_loggedin', true);
if (apiResult.result) {
f7App.views.main.router.navigate('/staff/');
} else {
$('#login-result').html(apiResult.message);
}
},
error: function (xhr, status, error) {
f7App.dialog.alert('Something went wrong. Please try again.', 'Error');
},
complete: function () {
// β
Hide preloader after AJAX completes
f7App.dialog.close();
}
});
});
});
// Page: Customer
$(document).on('page:init', '.page[data-name="customer"]', function () {
var sendCode = function(code,type)
{
if(type=='qr')
f7App.dialog.preloader('Scanning code…');
else
f7App.dialog.preloader('Sending code…');
$('.scan').prop('disabled', true);
$('.manually').prop('disabled', true);
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html('Code '+ code +' sent to boxoffice, plase wait…');
$.ajax({
url: `${ApiUrl}scan`,
method: 'POST',
data: {
token: TOKEN,
event_date_id: eventDateId,
order_code: code
},
success: function(result,status,xhr)
{
var apiResult = JSON.parse(result);
if ( apiResult.result )
{
$('#scan-result').removeClass('bg-dark').addClass('bg-success').html(apiResult.message+''+apiResult.seating+''+apiResult.tickets);
f7App.dialog.alert(apiResult.message, 'Scan Failed');
// reset input to set next code
$('[name="code"]').val(eventId +'.'+ eventDateId +'.');
}
else
{
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html(apiResult.message);
f7App.dialog.alert(apiResult.message, 'Scan Failed');
}
},
error: function(xhr,status,error)
{
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html('Code '+ result.text +' could not be sent to boxoffice, please try again');
f7App.dialog.alert(`${result.text} could not be sent to boxoffice, please try again`, 'Scan Failed');
},
complete: function()
{
$('.scan').prop('disabled', false);
$('.manually').prop('disabled', false);
f7App.dialog.close();
}
});
}
var scanError = function (error)
{
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html("Scanning failed: " + error);
//f7App.popup.open('#modal-scan-result');
};
var scanSuccess = function(result)
{
if (! result.cancelled )
{
if (eventDateId > -1)
{
sendCode(result.text,'qr');
}
else
{
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html('You must select an event date to scan tickets');
}
}
else
{
$('#scan-result').removeClass('bg-success').addClass('bg-dark').html('Scan was cancelled');
}
//f7App.popup.open('#modal-scan-result');
};
loadEvents('no');
$(document).on('click', '.reload-events', function()
{
loadEvents('no');
});
// $(document).on('click', '.open-event-detail', function () {
// const eventId = $(this).data('event-id');
// const eventDateId = $(this).data('event-date-id');
// const title = $(this).data('title');
// const time = $(this).data('time');
// const location = $(this).data('location');
// const code=eventId +'.'+ eventDateId +'.';
// // Pass data via route props
// f7App.views.main.router.navigate(`/event/${eventId}/${eventDateId}/`, {
// props: {
// title,
// time,
// location,
// eventId,
// eventDateId,
// code
// },
// });
// });
// load events click
// $('.reload-events').trigger('click');
$(document).on('click', '.customer-screen .events .list-group-item', function()
{
// $('.customer-screen .events .list-group-item').removeClass('active');
// $(this).addClass('active');
// $('#scan-info').html($(this).html());
eventId = $(this).attr('data-event-id');
eventDateId = $(this).attr('data-event-date-id');
$('[name="code"]').val(eventId +'.'+ eventDateId +'.');
// first scan on event date click
//cordova.plugins.barcodeScanner.scan(scanSuccess, scanError, scanConfig);
// f7App.popup.open('#modal-scan-result');
});
$(document).on('click', '.scan', function()
{
cordova.plugins.barcodeScanner.scan(scanSuccess, scanError, scanConfig);
});
$(document).on('click', '.manually', function()
{
sendCode($('[name="code"]').val(),'manual');
});
});
// $('ons-button').on('click', function(e) {
// ons.notification.alert('Button is tapped!');
// })
});
}
function loadEvents(staff)
{
f7App.dialog.preloader('Loading events…');
$.ajax({
url: `${ApiUrl}today`,
method: 'POST',
data: {
token: TOKEN
},
success: function(result,status,xhr)
{
console.log(result);
var events = JSON.parse(result);
if (events.result) {
$('.customer-screen ul.events').html('');
const weekday = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
$.each(events.data, function(i, val)
{
var eventDatetime = new Date(val.event_datetime);
var dateOptions = { year: '2-digit', month: '2-digit', day: '2-digit' };
var timeOptions = { hour12: true, hour: '2-digit', minute: '2-digit' };
var dayname=weekday[eventDatetime.getDay()];
var code=val.event_id +'.'+ val.event_date_id +'.';
if(staff=='no')
{
$('.customer-screen div.events').append(`
${val.venue_title}-${val.venue_full}
${dayname} at ${eventDatetime.toLocaleDateString('en-GB', dateOptions)} –
${eventDatetime.toLocaleTimeString('en-US', timeOptions)}
0/500
`);
}else{
$('.staff-screen div.events').append(`
${val.venue_full}
${dayname} at ${eventDatetime.toLocaleDateString('en-GB', dateOptions)} –
${eventDatetime.toLocaleTimeString('en-US', timeOptions)}
0/500
`);
}
});
}else{
$('.events').html(`${events.message}`);
}
},
error: function(xhr,status,error)
{
f7App.dialog.alert('Something went wrong. Please try again.', 'Error');
},
complete: function()
{
f7App.dialog.close();
}
});
}
when click on .manually button then f7 preloader showing multiple times and after scan failed alert it needs to click ok to 3 times to close alert in android apk
—
### β **The same function is being triggered multiple times or listeners are not scoped correctly**
—
## β Solution Steps
### β 1. **Prevent multiple bindings**
You're binding `sendCode` and click handlers **inside `page:init`**, but also globally (like `deviceready`). Each time the page is initialized or navigated, it **re-binds the same `.click` handlers again**, causing functions to fire **multiple times**.
—
### π§ Fix: **Use `.off('click').on('click')` to avoid multiple bindings**
Update this part:
$(document).on('click', '.manually', function()
{
sendCode($('[name="code"]').val(),'manual');
});
Change it to this:
$(document).off('click', '.manually').on('click', '.manually', function () {
sendCode($('[name="code"]').val(), 'manual');
});
Do the same for `.scan` and other dynamic buttons:
$(document).off('click', '.scan').on('click', '.scan', function () {
cordova.plugins.barcodeScanner.scan(scanSuccess, scanError, scanConfig);
});
—
### β 2. **Ensure `sendCode()` is not declared inside `page:init`**
If you're declaring `sendCode()` inside `page:init`, it gets **redefined on every page load**, which is bad.
π’ Move `sendCode()` outside (to global scope) or wrap it safely in a `window.sendCode = function() { … }` and avoid redefining it every time.
—
### β 3. **Avoid multiple `.dialog.preloader()` or `.dialog.alert()` calls**
Right now, you have:
f7App.dialog.alert(apiResult.message, 'Scan Failed');
f7App.dialog.close();
You should never call `.close()` after `.alert()` β alerts are modal, and **Framework7 stacks them if triggered quickly**.
π’ Fix this by removing unnecessary `f7App.dialog.alert()` after a preloader:
**Replace this block:**
f7App.dialog.alert(apiResult.message, 'Scan Failed');
f7App.dialog.close();
**With just:**
f7App.dialog.close();
f7App.dialog.alert(apiResult.message, 'Scan Failed');
π And ensure `.alert()` is **only called once**, not inside loops or after the dialog is already open.
—
## β Final Clean Example for `.manually` button
$(document).off('click', '.manually').on('click', '.manually', function () {
if (!window.eventDateId || eventDateId < 0) {
f7App.dialog.alert('Please select an event first.', 'Error');
return;
}
const code = $('[name="code"]').val();
sendCode(code, 'manual');
});
—
## β Summary
| Problem | Solution |
|——–|———-|
| Multiple dialogs or alerts | Use `.off().on()` for safe event binding |
| Multiple alerts stacking | Only call `f7App.dialog.alert()` once, never chain it after `preloader` |
| Redundant function definitions | Define `sendCode()` only once in global scope |
—
Let me know if you want me to cleanly refactor your whole `customer` page logic for clarity and performance.