I am hoping you can help. I am fairly new to Unit Testing. I have a Karma + Jasmine set up which is running a PhantomJS browser. This is all good.
What I am struggling with is I have a link on the page, when this link is clicked it injects some HTML. I want to test that the HTML has been injected.
Now at this point, I have the test working but only sometimes, from what I can figure out if my JS runs fast enough the HTML gets injected before the expect() is run. If not the test fails.
How can I make my Jasmine test wait for all JS to finish executing before the expect() is run?
The test in question is it("link can be clicked to open a modal", function() {
modal.spec.js
const modalTemplate = require('./modal.hbs');
import 'regenerator-runtime/runtime';
import 'core-js/features/array/from';
import 'core-js/features/array/for-each';
import 'core-js/features/object/assign';
import 'core-js/features/promise';
import Modal from './modal';
describe("A modal", function() {
beforeAll(function() {
const data = {"modal": {"modalLink": {"class": "", "modalId": "modal_1", "text": "Open modal"}, "modalSettings": {"id": "", "modifierClass": "", "titleId": "", "titleText": "Modal Title", "closeButton": true, "mobileDraggable": true}}};
const modal = modalTemplate(data);
document.body.insertAdjacentHTML( 'beforeend', modal );
});
it("link exists on the page", function() {
const modalLink = document.body.querySelector('[data-module="modal"]');
expect(modalLink).not.toBeNull();
});
it("is initialised", function() {
spyOn(Modal, 'init').and.callThrough();
Modal.init();
expect(Modal.init).toHaveBeenCalled();
});
it("link can be clicked to open a modal", function() {
const modalLink = document.body.querySelector('[data-module="modal"]');
modalLink.click();
const modal = document.body.querySelector('.modal');
expect(modal).not.toBeNull();
});
afterAll(function() {
console.log(document.body);
// TODO: Remove HTML
});
});
EDIT - More Info
To further elaborate on this, The link Jasmine 2.0 how to wait real time before running an expectation put in the comments has helped me understand a bit better, I think. So what we are saying it we want to spyOn the function and wait for it to be called and then initiate a callback which then resolves the test.
Great.
My next issue is, if you look at the structure of my ModalViewModel class below, I need to be able to spyOn insertModal() to be able to do this, but the only function that is accessible in init(). What would I do to be able to move forward with this method?
import feature from 'feature-js';
import { addClass, removeClass, hasClass } from '../../01-principles/utils/classModifiers';
import makeDraggableItem from '../../01-principles/utils/makeDraggableItem';
import '../../01-principles/utils/polyfil.nodeList.forEach'; // lt IE 12
const defaultOptions = {
id: '',
modifierClass: '',
titleId: '',
titleText: 'Modal Title',
closeButton: true,
mobileDraggable: true,
};
export default class ModalViewModel {
constructor(module, settings = defaultOptions) {
this.options = Object.assign({}, defaultOptions, settings);
this.hookModalLink(module);
}
hookModalLink(module) {
module.addEventListener('click', (e) => {
e.preventDefault();
this.populateModalOptions(e);
this.createModal(this.options);
this.insertModal();
if (this.options.closeButton) {
this.hookCloseButton();
}
if (this.options.mobileDraggable && feature.touch) {
this.hookDraggableArea();
}
addClass(document.body, 'modal--active');
}, this);
}
populateModalOptions(e) {
this.options.id = e.target.getAttribute('data-modal');
this.options.titleId = `${this.options.id}_title`;
}
createModal(options) {
// Note: As of ARIA 1.1 it is no longer correct to use aria-hidden when aria-modal is used
this.modalTemplate = `<section id="${options.id}" class="modal ${options.modifierClass}" role="dialog" aria-modal="true" aria-labelledby="${options.titleId}" draggable="true">
${options.closeButton ? '<a href="#" class="modal__close icon--cross" aria-label="Close" ></a>' : ''}
${options.mobileDraggable ? '<a href="#" class="modal__mobile-draggable" ></a>' : ''}
<div class="modal__content">
<div class="row">
<div class="columns small-12">
<h2 class="modal__title" id="${options.titleId}">${options.titleText}</h2>
</div>
</div>
</div>
</section>`;
this.modal = document.createElement('div');
addClass(this.modal, 'modal__container');
this.modal.innerHTML = this.modalTemplate;
}
insertModal() {
document.body.appendChild(this.modal);
}
hookCloseButton() {
this.closeButton = this.modal.querySelector('.modal__close');
this.closeButton.addEventListener('click', (e) => {
e.preventDefault();
this.removeModal();
removeClass(document.body, 'modal--active');
});
}
hookDraggableArea() {
this.draggableSettings = {
canMoveLeft: false,
canMoveRight: false,
moveableElement: this.modal.firstChild,
};
makeDraggableItem(this.modal, this.draggableSettings, (touchDetail) => {
this.handleTouch(touchDetail);
}, this);
}
handleTouch(touchDetail) {
this.touchDetail = touchDetail;
const offset = this.touchDetail.moveableElement.offsetTop;
if (this.touchDetail.type === 'tap') {
if (hasClass(this.touchDetail.eventObject.target, 'modal__mobile-draggable')) {
if (offset === this.touchDetail.originY) {
this.touchDetail.moveableElement.style.top = '0px';
} else {
this.touchDetail.moveableElement.style.top = `${this.touchDetail.originY}px`;
}
} else if (offset > this.touchDetail.originY) {
this.touchDetail.moveableElement.style.top = `${this.touchDetail.originY}px`;
} else {
this.touchDetail.eventObject.target.click();
}
} else if (this.touchDetail.type === 'flick' || (this.touchDetail.type === 'drag' && this.touchDetail.distY > 200)) {
if (this.touchDetail.direction === 'up') {
if (offset < this.touchDetail.originY) {
this.touchDetail.moveableElement.style.top = '0px';
} else if (offset > this.touchDetail.originY) {
this.touchDetail.moveableElement.style.top = `${this.touchDetail.originY}px`;
}
} else if (this.touchDetail.direction === 'down') {
if (offset < this.touchDetail.originY) {
this.touchDetail.moveableElement.style.top = `${this.touchDetail.originY}px`;
} else if (offset > this.touchDetail.originY) {
this.touchDetail.moveableElement.style.top = '95%';
}
}
} else {
this.touchDetail.moveableElement.style.top = `${this.touchDetail.moveableElementStartY}px`;
}
}
removeModal() {
document.body.removeChild(this.modal);
}
static init() {
const instances = document.querySelectorAll('[data-module="modal"]');
instances.forEach((module) => {
const settings = JSON.parse(module.getAttribute('data-modal-settings')) || {};
new ModalViewModel(module, settings);
});
}
}
UPDATE
After working through it has been discovered that .click() events are asynchronous which is why I am gettnig the race issue. Documentation & Stack Overflow issues thoughtout the web recommend using createEvent() and dispatchEvent() as PhantomJs does not understand new MouseEvent().
Here is my code which is now trying to do this.
modal.spec.js
// All my imports and other stuff
// ...
function click(element){
var event = document.createEvent('MouseEvent');
event.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
element.dispatchEvent(event);
}
describe("A modal", function() {
// Some other tests
// Some other tests
it("link can be clicked to open a modal", function() {
const modalLink = document.body.querySelector('[data-module="modal"]');
click(modalLink);
const modal = document.body.querySelector('.modal');
expect(modal).not.toBeNull();
});
// After all code
// ...
});
Unfortunately this is producting the same results. 1 step closer but not quite there.
After a touch of research, it looks as though your use of the click event is triggering an asynchronous event loop essentially saying "Hey set this thing to be clicked and then fire all the handlers"
Your current code can't see that and has no real way of waiting for it. I do believe you should be able to build and dispatch a mouse click event using the info here.
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent
I think that should allow you to build a click event and dispatch it onto your element. The difference is that dispatchEvent is synchronous - it should block your test until the click handlers have completed. That should allow you to do your assertion without failures or race conditions.
I have finally found a solution.
There are 2 parts to this, the first part came from #CodyKnapp. His insight into a click() function running asynchronously helped to solve the first part of the issue.
Here is the code for this part.
modal.spec.js
// All my imports and other stuff
// ...
function click(element){
var event = document.createEvent('MouseEvent');
event.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
element.dispatchEvent(event);
}
describe("A modal", function() {
// Some other tests
// Some other tests
it("link can be clicked to open a modal", function() {
const modalLink = document.body.querySelector('[data-module="modal"]');
click(modalLink);
const modal = document.body.querySelector('.modal');
expect(modal).not.toBeNull();
});
// After all code
// ...
});
This allowed for the code to run synchronously.
The second part was a poor understanding on my part of how to write Jasmine tests. In my original tests I was running Modal.init() inside of it("is initialised", function() { when actually I want to be running this inside of beforeAll(). This fixed the issue I had where my tests would not always be successful.
Here is my final code:
modal.spec.js
const modalTemplate = require('./modal.hbs');
import '#babel/polyfill';
import Modal from './modal';
function click(element){
var event = document.createEvent('MouseEvent');
event.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
element.dispatchEvent(event);
}
describe("A modal", function() {
beforeAll(function() {
const data = {"modal": {"modalLink": {"class": "", "modalId": "modal_1", "text": "Open modal"}, "modalSettings": {"id": "", "modifierClass": "", "titleId": "", "titleText": "Modal Title", "closeButton": true, "mobileDraggable": true}}};
const modal = modalTemplate(data);
document.body.insertAdjacentHTML( 'beforeend', modal );
spyOn(Modal, 'init').and.callThrough();
Modal.init();
});
it("link exists on the page", function() {
const modalLink = document.body.querySelector('[data-module="modal"]');
expect(modalLink).not.toBeNull();
});
it("is initialised", function() {
expect(Modal.init).toHaveBeenCalled();
});
it("link can be clicked to open a modal", function() {
const modalLink = document.body.querySelector('[data-module="modal"]');
click(modalLink);
const modal = document.body.querySelector('.modal');
expect(modal).not.toBeNull();
});
afterAll(function() {
console.log(document.body);
// TODO: Remove HTML
});
});
Related
I try to build a simple Text Editor using Electron.
At the moment I want to add a custom title bar which doesn't really work as the buttons are not clickable...
I added an onclick tag to the buttons.
main.js:
//-- variables --\\
const { BrowserWindow, app, Menu, dialog, ipcMain } = require("electron")
let window
let filePath = null //currently opened file
let openFiles = [] //opened files
//-- startup --\\
app.whenReady().then(function()
{
createWindow()
createMenu()
})
//-- functions --\\
function createWindow()
{
window = new BrowserWindow
({
width: 1000,
height: 600,
frame: false,
icon: "./assets/icon.png",
darkTheme: true,
autoHideMenuBar: true,
webPreferences:
{
nodeIntegration: true,
contextIsolation: false
}
})
//window.maximize()
window.loadFile("./frontend/index.html")
window.webContents.openDevTools()
}
function createMenu()
{
Menu.setApplicationMenu(Menu.buildFromTemplate
([
{
label: "File",
submenu:
[
{
label: "Save",
click: saveFile,
accelerator: "CmdOrCtrl+S"
},
{
label: "Open",
click: openFile,
accelerator: "CmdOrCtrl+O"
},
{
label: "New",
click: newFile,
accelerator: "CmdOrCtrl+N"
}
]
}
]))
}
async function promptFilePathOpen()
{
await dialog.showOpenDialog
({ properties: ["openFile"] }).then(function(res)
{
if(res.canceled) return
filePath = res.filePaths[0]
})
}
async function promptFilePathSave()
{
await dialog.showSaveDialog().then(function(res)
{
if(res.canceled) return
filePath = res.filePath
})
}
async function openFile()
{
await promptFilePathOpen()
//openFiles.push(filePath)
window.webContents.send("crd-openFile", filePath)
}
async function saveFile()
{
if(filePath == null || undefined) await promptFilePathSave()
window.webContents.send("crd-saveFile", filePath)
}
async function newFile()
{
filePath = null
await promptFilePathSave()
window.webContents.send("crd-resetEditor")
window.webContents.send("crd-saveFile", filePath)
}
//-- listeners --\\
app.on("window-all-closed", function()
{
if(process.platform != "darwin") app.quit()
})
ipcMain.on("crd-minimizeWindow", function() //does not get called by the renderer
{
//coming soon
})
ipcMain.on("crd-toggleWindowSize", function() //does not get called by the renderer
{
//coming soon
})
ipcMain.on("crd-closeWindow", function() //does not get called by the renderer
{
console.log("quit")
})
renderer/script.js:
//-- imports --\\
const { ipcRenderer } = require("electron")
const fs = require("fs")
const editorElem = document.querySelector("textarea.editor")
//-- functions --\\
function minimizeWindow() // does not get called by the button (index.html)
{
ipcRenderer.send("crd-minimizeWindow")
}
function toggleWindowSize() // does not get called by the button (index.html)
{
ipcRenderer.send("crd-toggleWindowSize")
}
function closeWindow() // does not get called by the button (index.html)
{
ipcRenderer.send("crd-closeWindow")
}
//-- listeners --\\
ipcRenderer.on("crd-openFile", function(e, path)
{
editorElem.value = fs.readFileSync(path, "utf-8")
})
ipcRenderer.on("crd-saveFile", function(e, path)
{
fs.writeFile(path, editorElem.value , function(res, err)
{
if(err) throw err
})
})
ipcRenderer.on("crd-resetEditor", function()
{
editorElem.value = ""
})
The entire code is avalable on my GitHub, because I do not want the whole question consisting of code :)
Two issues here:
You defined functions like closeWindow, but you didn't actually add an event listener for them. You mention onclick but I can't see that in your code. So the first step would be to add document.querySelector('.closeWindow').addEventListener('click', closeWindow) .
You made the whole title bar draggable, including the buttons. That means that the role of the buttons is also a draggable area, so when you click them, you start the drag operation instead of sending a click event. The solution is therefore to make sure the button area does not have the -webkit-app-region: drag style but only the area left to them has. This will probably require you to redesign the HTML layout for the title bar a bit, since this won't work well with the whole thing being a grid.
For more details, see this tutorial.
i try to use scanner.js but the same error keeps on showing
i have a problem with websocket connection when i try using scanner.js from Asprise.
i installed scanner.js using npm i scanner.js and imported it in my react code. i even added the following script script in my html page and it does notwork.
import React from "react";
import scanner from 'scanner-js';
let scanRequest = {
"use_asprise_dialog": true, // Whether to use Asprise Scanning Dialog
"show_scanner_ui": true, // Whether scanner UI should be shown
"twain_cap_setting" : {
"ICAP_PIXELTYPE" : "TWPT_RGB", // Color
"ICAP_XRESOLUTION" : "100", // DPI: 100
"ICAP_YRESOLUTION" : "100",
"ICAP_SUPPORTEDSIZES" : "TWSS_USLETTER" // Paper size: TWSS_USLETTER, TWSS_A4, ...
},
"output_settings": [{
"type": "return-base64",
"format": "pdf",
"thumbnail_height": 200,
}]
};
/** Triggers the scan */
const scan = () => {
scanner.scan(displayImagesOnPage, scanRequest);
}
/** Processes the scan result */
const displayImagesOnPage = (successful, mesg, response) => {
if (!successful) { // On error
console.error('Failed: ' + mesg);
return;
}
if (successful && mesg != null && mesg.toLowerCase().indexOf('user cancel') >= 0) { // User cancelled.
console.info('User cancelled');
return;
}
let scannedImages = scanner.getScannedImages(response, true, false); // returns an array of ScannedImage
for (let i = 0;
(scannedImages instanceof Array) && i < scannedImages.length; i++) {
let scannedImage = scannedImages[i];
let elementImg = scanner.createDomElementFromModel({
'name': 'img',
'attributes': {
'class': 'scanned',
'src': scannedImage.src
}
});
(document.getElementById('images') ? document.getElementById('images') : document.body).appendChild(elementImg);
}
}
export const Scanner = () => {
return (
<div>
<h2>Scanner.js TEST</h2>
<button type="button" onClick={()=>scan()}>
Scan
</button>
<div id="images"></div>
</div>
);
};
export default Scanner;
I am working on a webapp with drag 'n drop functionality.
Up to now I used basic HTML5 Dragging but now I switched to using the library interact.js.
I don't know if my problem is specific to this library or of more general kind:
Events when dragging and dropping usually fire multiple times (if I have watched it correctly, it also seems to always be exactly 4 times, but no guarantee on that).
I am also using Vue.js and this is my code:
<template>
<v-card
elevation="0"
:id="id"
class="board device-dropzone"
>
<slot class="row"/>
<div
Drop Card here
</div>
</v-card>
</template>
In the slot, an image and div with text get added. Also this is the script:
<script>
import interact from 'interactjs';
export default {
name: 'Devices',
props: ['id', 'acceptsDrop'],
data() {
return {
extendedHover: false,
hoverEnter: false,
timer: null,
totalTime: 2,
};
},
methods: {
resetHover() {
alert('reset');
},
drop(e) {
let wantedId = e.relatedTarget.id.split('-')[0];
console.log(wantedId);
console.warn(e.target);
e.target.classList.remove('hover-drag-over');
this.extendedHover = false;
console.log('-------------dropped');
console.warn('dropped onto device');
this.$emit('dropped-component', cardId);
e.target.classList.remove('hover-drag-over'); */
},
dragenter() {
console.log('------------dragenter');
this.hoverEnter = true;
setTimeout(() => {
this.extendedHover = true;
console.log('extended hover detected');
}, 2000);
} */
this.timerID = setTimeout(this.countdown, 3000);
},
dragover(e) {
if (this.acceptsDrop) {
e.target.classList.add('hover-drag-over');
}
},
dragleave(e) {
if (this.acceptsDrop) {
clearInterval(this.timer);
this.timer = null;
this.totalTime = 2;
e.target.classList.remove('hover-drag-over');
this.extendedHover = false;
this.hoverEnter = false;
console.log('................');
console.warn(this.extendedHover);
// this.$emit('cancel-hover');
}
},
countdown() {
console.log('!!!!!!!!!!!!!!!!!!!!');
if (this.hoverEnter === true) {
this.extendedHover = true;
console.log('-------------------');
console.warn(this);
console.warn(this.extendedHover);
this.$emit('long-hover');
}
},
},
mounted() {
// enable draggables to be dropped into this
const dropzone = this;
interact('.device-dropzone').dropzone({
overlap: 0.9,
ondragenter: dropzone.dragenter(),
ondrop: function (event) {
dropzone.drop(event);
},
})
},
};
</script>
The draggable component is this one:
<template>
<v-card
class="primary draggable-card"
:id = "id"
:draggable = "false"
#dragover.stop
ref="interactElement"
>
<slot/>
</v-card>
With the script:
<script>
import interact from 'interactjs';
export default {
props: ['id', 'draggable'],
data() {
return {
isInteractAnimating: true,
position: { x: 0, y: 0 },
};
},
methods: {
/* dragStart: (e) => {
e.stopPropagation(); // so dragStart of ParentBoard does not get triggered as well
// eslint-disable-next-line
const target = e.target;
e.dataTransfer.setData('card_id', target.id);
e.dataTransfer.setData('type', 'widget');
// for some delay
setTimeout(() => {
console.log('started dragging');
}, 0);
}, */
dragEndListener: (event) => {
console.warn('+++++++++++++++++++++++++++');
// console.warn(event.currentTarget.id);
if (document.getElementById(event.currentTarget.id)) {
event.currentTarget.parentNode.removeChild(event.currentTarget);
}
},
dragMoveListener: (event) => {
/* eslint-disable */
var target = event.target;
// keep the dragged position in the data-x/data-y attributes
const xCurrent = parseFloat(target.getAttribute('data-x')) || 0;
const yCurrent = parseFloat(target.getAttribute('data-y')) || 0;
const valX = xCurrent + event.dx;
const valY = yCurrent + event.dy;
// translate the element
event.target.style.transform =
`translate(${valX}px, ${valY}px)`
// update the postion attributes
target.setAttribute('data-x', x);
target.setAttribute('data-y', y);
}
/* eslint-enable */
},
mounted() {
const element = this.$refs.interactElement;
console.log(element);
// interact(element).draggable({
const component = this;
interact('.draggable-card')
.draggable({
manualStart: true,
onmove: component.dragMoveListener,
onend:component.dragEndListener,
})
.on('move', function (event) {
var interaction = event.interaction;
// if the pointer was moved while being held down
// and an interaction hasn't started yet
if (interaction.pointerIsDown && !interaction.interacting()) {
var original = event.currentTarget;
// create a clone of the currentTarget element
const clone = event.currentTarget.cloneNode(true);
clone.id = clone.id + "-clone";
clone.classname += " dragged-clone";
// insert the clone to the page
document.body.appendChild(clone);
clone.style.opacity = 0.5;
// start a drag interaction targeting the clone
interaction.start({ name: 'drag' },
event.interactable,
clone);
}
})
.on('end', function (event) {
console.error('end drag');
});
},
/* eslint-enable */
};
</script>
In general the dragging and dropping works.
But I don't get why e.g. the drop-event would trigger four times when only dropping a single card.
Can anybody help me with this?
I was facing a similar issue using the same framework and library. Since there was some logic based on the event firing, it was breaking my app when it fired multiple times.
I suspected that the issue was related to bubbling of events.
The solution in my case was therefore to add event.stopImmediatePropagation() inside my drop(event) handler.
As noted in the referenced article, one should take care when stopping event bubbling that it doesn't have unforeseen consequences elsewhere.
I have a vue component for video upload, where I am warning a user when he tries to navigate away during the video upload that he will lose the file if he does so, like this:
ready() {
window.onbeforeunload = () => {
if (this.uploading && !this.uploadingComplete && !this.failed) {
this.confirm('Are you sure you want to navigate away? Your video won't be uploaded if you do so!');
}
}
}
I am using sweetalert to alert the user about it. But how can I then make it stay on the same page, and prevent the navigation away before he confirms that he wants to navigate away?
This is the whole component:
<script>
function initialState (){
return {
uid: null,
uploading: false,
uploadingComplete: false,
failed: false,
title: null,
link: null,
description: null,
visibility: 'private',
saveStatus: null,
fileProgress: 0
}
}
export default {
data: function (){
return initialState();
},
methods: {
fileInputChange() {
this.uploading = true;
this.failed = false;
this.file = document.getElementById('video').files[0];
this.store().then(() => {
var form = new FormData();
form.append('video', this.file);
form.append('uid', this.uid);
this.$http.post('/upload', form, {
progress: (e) => {
if (e.lengthComputable) {
this.updateProgress(e)
}
}
}).then(() => {
this.uploadingComplete = true
}, () => {
this.failed = true
});
}, () => {
this.failed = true
})
},
store() {
return this.$http.post('/videos', {
title: this.title,
description: this.description,
visibility: this.visibility,
extension: this.file.name.split('.').pop()
}).then((response) => {
this.uid = response.json().data.uid;
});
},
update() {
this.saveStatus = 'Saving changes.';
return this.$http.put('/videos/' + this.uid, {
link: this.link,
title: this.title,
description: this.description,
visibility: this.visibility
}).then((response) => {
this.saveStatus = 'Changes saved.';
setTimeout(() => {
this.saveStatus = null
}, 3000)
}, () => {
this.saveStatus = 'Failed to save changes.';
});
},
updateProgress(e) {
e.percent = (e.loaded / e.total) * 100;
this.fileProgress = e.percent;
},
confirm(message) {
swal({
title: message,
text: null,
type: "warning",
showCancelButton: true,
cancelButtonText: "Cancel",
cancelButtonColor: '#FFF',
confirmButtonColor: "#2E112D",
confirmButtonText: "Yes, delete"
}).then(function(){
this.$data = initialState();
}.bind(this), function(dismiss) {
// dismiss can be 'overlay', 'cancel', 'close', 'esc', 'timer'
if (dismiss === 'cancel') { // you might also handle 'close' or 'timer' if you used those
// ignore
} else {
throw dismiss;
}
})
}
},
ready() {
window.onbeforeunload = () => {
if (this.uploading && !this.uploadingComplete && !this.failed) {
this.confirm('Are you sure you want to navigate away? Your video won't be uploaded if you do so!');
}
}
}
}
</script>
Mozilla documentation suggests
window.onbeforeunload = function(e) {
var dialogText = 'Dialog text here';
e.returnValue = dialogText;
return dialogText;
};
and also states that:
Since 25 May 2011, the HTML5 specification states that calls to window.alert(), window.confirm(), and window.prompt() methods may be ignored during this event. See the HTML5 specification for more details.
Source contains many other details regarding reasons and what to expect from modern browsers.
This question seems to be a duplicate of yours.
This answer suggests that to avoid weird browser behaviour you should set handler only when it's to prevent something (that is while navigating away should trigger a confirmation dialog)
But how can I then make it stay on the same page, and prevent the navigation away before he confirms that he wants to navigate away?
Add return false; to stop the event.
if (this.uploading && !this.uploadingComplete && !this.failed) {
this.confirm("Are you sure you want to navigate away? Your video won't be uploaded if you do so!");
return false; // <==== add this
}
return false; does 3 separate things when you call it :
event.preventDefault(); – It stops the browsers default behaviour.
event.stopPropagation(); – It prevents the event from propagating (or “bubbling up”) the DOM.
Stops callback execution and returns immediately when called.
I created an application and it worked fine, most of the functionality is just the application reacting to different events. I would like to implement a router so that users would be able to show their search results with other users etc. The problem is the router appears to be set up correctly, as when i use it to append things directly to the body everything works as expected. When I try to use the router to trigger events though nothing happens, any help would be greatly appreciated. It is worth mentioning I suppose that this is not the complete code but just the parts that seemed relevant to the issue I am experiencing.
IEG = new Backbone.Marionette.Application();
IEG.addRegions({
searchBox: '#searchBox',
resultBox: '#resultBox',
modalBox: '#modalBox',
recipientBox: '#recipientBox',
confirmBox: '#confirmToggleActive'
});
IEG.vent = _.extend({}, Backbone.Events);
IEG.Router = Backbone.Marionette.AppRouter.extend({
routes: {
'': 'index'
},
index: function () {
IEG.vent.trigger("default"); ////TRIGGER EVENT DOES NOT WORK
//$(document.body).append("Index route has been called..");
}
});
SearchBoxView = Backbone.Marionette.ItemView.extend({
template: Handlebars.templates['search'],
events: {
'click #addGroup': 'addGroup',
'keyup #searchStr': 'evaluateSearch'
},
addGroup: function () {
IEG.vent.trigger("addGroup");
},
clearValidationMsg: function () {
$('#searchErrors').html("");
},
evaluateSearch: function (e) {
console.log("keyup DO SOMETHING> ", e.keyCode);
if (e.keyCode === 13) {///press enter execute search
var searchStr = $('#searchStr').val().trim();
if (searchStr) {
IEG.vent.trigger("searchGroups", searchStr);
}
}
else if (e.keyCode === 8 || e.keyCode === 46) {//backspace and delete keys
var searchStr = $('#searchStr').val().trim();
if (!searchStr) {//when searchbar is cleared show all groups
IEG.vent.trigger("searchGroups", null)
}
}
},
validateEmail: function (searchStr) {
return /^.+#.+\..+$/.test(address);
}
});
$(document).ready(function () {
IEG.start();
new IEG.Router;
Backbone.history.start();
IEG.vent.on("default", function () {
var SBV = new SearchBoxView();
IEG.searchBox.show(SBV);
IEG.searchColl = new GroupEntries();
IEG.searchColl.fetch({
data: {
cmd: 0, //search groups
searchStr: null //if null show all groups
},
success: function (data) {
searchResults = new SearchResultsView({ collection: IEG.searchColl });
IEG.resultBox.show(searchResults);
}
});
});
});
Make sure your event listeners are defined before the event is triggered. Most likely, the event trigger is working fine, but your event listener is registered after the router has triggered the event and nothing happens...