Template.onRendered() called too early - javascript

I have hit a strange problem. I am using hammer.js to configure long-press events, and attaching the event watcher within the Template.onRendered() callback. This works perfectly on my local development machine. However, when I deploy to a server, it seems that onRendered() is being fired before the template is finished rendering in the browser. The jQuery selector is empty. I've had to resort to setting a timer to make sure the template is rendered before configuring the event handler. Alternatively, I've tried configuring the event handler within a Tracker.afterFlush() in place of setTimeout(), but the template is still not rendered when this fires.
It seems that I shouldn't have to use setTimeout() here. Am I doing something wrong or out of order?
Template.CATEGORIES.onRendered(function () {
Meteor.setTimeout(function () {
console.log('setting up hammer object', this.$('.collapsible'));
var h = this.$('.collapsible').hammer({domEvents:true});
h.on('tap press swipe pan', '.collapsible-header', function (ev) {
// get the ID of the shopping list item object
var target = ev.target;
var $target = $(target);
var type = ev.type;
var $header = $target;
if (Collapse.isChildrenOfPanelHeader($target)) {
$header = Collapse.getPanelHeader($target);
}
console.log('Firing ', type, ' on ', $header);
Kadira.trackError('debug', 'Firing ' + type + ' on ' + $header);
// handler for checkbox
if (type === 'tap' && $target.is('label')) {
var data = Blaze.getData(target);
var TS = data.checkedTS;
ev.preventDefault();
data.checked = !data.checked;
console.log('Checkbox handler', data);
if (data.checked && !TS) {
TS = new Date()
} else if (!data.checked) {
TS = null
}
// Set the checked property to the opposite of its current value
ShoppingList.update(data._id, {
$set: {checked: data.checked, checkedTS: TS}
});
} else if (type === 'tap' && $target.has('.badge')) {
// if the user taps anywhere else on an item that has a child with .badge class
// this item has deals. Toggle the expand.
console.log('badge handler');
$header.toggleClass('active');
Collapse.toggleOpen($header);
} else if (type === 'press' || type === 'swipe' || type === 'pan') {
// remove any selected deals
var itemID, item;
var $label = $header.find('label');
console.log('long press handler');
if ($label) {
itemID = $label.attr('for');
item = ShoppingList.findOne(itemID);
if (item && item.deal) {
Deals.update(item.deal._id, {$set: {'showOnItem': false}});
ShoppingList.update(itemID, {$set: {'deal': null}});
}
}
}
})
}, 2000);
});

Someone answered this the other day but must have withdrawn their answer. They suggested that the template was actually rendered, but that the data subscription had not finished replicating yet. They were right.
I was able to validate that I need to wait until the data subscription finishes prior to setting up my java script events.

Related

Creating events for dynamically created Buttons

I am new to jquery. I was creating a dynamic query for generating buttons. Here the problem I am facing is - the button starts firing during the Loading of the button event. The query is given below.
priv.buildFilter = function () {
$.each(priv.buttonsNames, function (index, value) {
var divButton = $("<div>");
if (priv.lineAuditData.endDateTime == null)
priv.filterButtons.buttons[index] = $("<button>")
.attr("Id", "StartAudit").addClass("btn btn-primary")
.addClass("btn-success").append($("<i>").addClass("fas fa-play").css("font-size","25px")
.css("margin-top", "27px"))
//.click("click", function (event) {
// if (event.data !== undefined)
// priv.startAudit();
//})
.click(this, priv.startAudit)
;
}
}
priv.startAudit = function () {
alert("ON");
}
Required OutPut : Button need not fires during the filter button binding. But needs to fire when clicking the button
Kindly help me out, with relevant documents

mutationobserver firing when no change and method not found issue

I've placed an observer on an element using MutationObserver. In a one use case it works exactly as expected and fires after a change but in another user action where the element has not changed it appears to be firing - but in doing so it comes up with a method not found error, which doesn't appear in the first use case using the same observer.
The observer watches for an update within an element which gets updated with an image as a user selects an image.
In the working case a user selects an image from a list of images, it then updates and the observer fires - all great.
In the non-working case a user uploads an image - at this point though no update has happened to the target element (which is in view but below a colorbox.(not sure if that's relevant).
The firing itself would not normally be a problem but within the observer callback it calls a method which in the second case it says is not defined.
So in the first instance there are no errors but in the second instance:
I get an error _this.buttons is not a function at MutationObserver.callback
The code is being compiled with webpack
1. Why is the observer firing when the doesn't appear to the type of change being observed?
Why does this error occur in this scenario - when the method appears to exist and works as expected when there is a change?
Any help appreciated
here's the code - this class manages the actions for a page - I've removed some code to try and keep it brief (but still a bit lengthy - refactoring to be done):
First, here's the code of the observer:
const callback = (mutationsList, observer) =>{
// Use traditional 'for loops' for IE 11
for(let mutation of mutationsList) {
if (mutation.type === 'childList') {
console.log('A child node has been added or removed.');
module.buttons();
module.initialiseControls();
}
else if (mutation.type === 'attributes') {
console.log('The ' + mutation.attributeName + ' attribute was modified.');
}
}
};
And here's the class in which the observer method is contained.
export let fileImageWidgetControls = class {
constructor({
previewBtn = '.preview-image',
addBtn = '#add-image',
replaceBtn = '#replace-image',
removeBtn = '#remove-image'
} = {}) {
this.options = {
previewBtn: previewBtn,
addBtn: addBtn,
replaceBtn: replaceBtn,
removeBtn: removeBtn
}
this.filemanager = new filemanagerHandler; //file selector class
this.imageWidget = new updateWidget; //handles updating the image
this.initialiseControls(); //sets up the page controls
this.observer(); //sets up the observer
}
openFileManager = () =>{
//open colbox (which opens filemanager page
//When filemanager loaded then initialise filemanager
$(document).bind('cbox_complete', ()=>{
console.log('Colbox complete');
this.filemanager.init();
});
//handle colbox closing and update image in widget (if needed)
$(document).bind('cbox_closed', ()=>{
let selectedAsset = this.filemanager.getSelectedAsset();
if(selectedAsset) {
this.imageWidget.update(selectedAsset.filename);
}
});
colBox.init({
href: this.options.serverURL
});
colBox.colorbox()
}
remove = ()=> {
//clear file and update visible buttons
this.buttons();
}
/**
* preview the image in a colorbox
* #param filename
*/
preview = function () {
//open image in preview
}
/**
* select image via filemanager
*/
select = () =>{
console.log('select');
this.openFileManager();
}
replace = () => {
// image already exists in widget but needs replacing
console.log('replace');
this.openFileManager();
}
initialiseControls = () => {
console.log('init controls');
//preview button
$(this.options.previewBtn).on('click', (e) => {
e.preventDefault();
this.preview();
}).attr('disabled', false);
$('#img-preview-link').on('click', (e)=> {
e.preventDefault();
this.preview();
});
// add button
$(this.options.addBtn).on('click', (e) => {
e.preventDefault();
this.select();
}).attr('disabled', false);
//replace button
$(this.options.replaceBtn).on('click', (e) => {
e.preventDefault();
this.replace();
}).attr('disabled', false);
//remove button
$(this.options.removeBtn).on('click', (e) => {
e.preventDefault();
this.remove();
}).attr('disabled', false);
this.buttons();
}
//set an observer to watch preview image for changes
observer= ()=> {
const module = this;
const targetNode = document.getElementById('image-preview-panel');
const config = { attributes: true, childList: true, subtree: true };
const callback = (mutationsList, observer) =>{
// Use traditional 'for loops' for IE 11
for(let mutation of mutationsList) {
if (mutation.type === 'childList') {
console.log('A child node has been added or removed.');
module.buttons();
module.initialiseControls();
}
else if (mutation.type === 'attributes') {
console.log('The ' + mutation.attributeName + ' attribute was modified.');
}
}
};
const observer = new MutationObserver(callback);
observer.observe(targetNode, config);
}
buttons = function() {
let imagePreview = $('#image-preview');
if(imagePreview.data('updated')=== true && imagePreview.data('updated') !== "false") {
console.log('image present');
$(this.options.addBtn).fadeOut().attr('disabled', true);
$(this.options.removeBtn).fadeIn().attr('disabled', false);
$(this.options.replaceBtn).fadeIn().attr('disabled', false);
$(this.options.previewBtn).fadeIn().attr('disabled', false);
} else {
console.log('image not present', imagePreview.data());
console.log('image element:', imagePreview);
$(this.options.addBtn).fadeIn().attr('disabled', false);
$(this.options.removeBtn).fadeOut().attr('disabled', true);
$(this.options.replaceBtn).fadeOut().attr('disabled', true);
$(this.options.previewBtn).fadeOut().attr('disabled', true);
}
}
}
I copied code from a tutorial hence some of the comments until I refactor
Added const module = this; within the method and referenced that within the nested function and now pointing to the correctthis`

How to make ondblclick event works on phone?

I want to achieve the double click event on a specific div like this:
<div id="divID" ondblclick = 'alert("double click!!");' >
it worked on the google chrome browser but when I open it with phone it didn't work, by the way the single click worked.
ps: i added this two things
<meta name="viewport" content="width=device-width, initial scale=1,user-scalable=no">
and this
body {
-ms-touch-action: manipulation;
touch-action: manipulation;}
but it didnt work!
I got the same issue. On touch devices, if you want to detect a double-tap gesture and you use the ondblclick event in most cases it will not work and also the problem is it will also fire an onclick. One of the solution is to implement a double tap detection pattern using the following code sample:
var doubletapDeltaTime_ = 700;
var doubletap1Function_ = null;
var doubletap2Function_ = null;
var doubletapTimer = null;
function tap(singleTapFunc, doubleTapFunc) {
if (doubletapTimer==null) {
// First tap, we wait X ms to the second tap
doubletapTimer_ = setTimeout(doubletapTimeout_, doubletapDeltaTime_);
doubletap1Function_ = singleTapFunc;
doubletap2Function_ = doubleTapFunc;
} else {
// Second tap
clearTimeout(doubletapTimer);
doubletapTimer_ = null;
doubletap2Function_();
}
}
function doubletapTimeout() {
// Wait for second tap timeout
doubletap1Function_();
doubleTapTimer_ = null;
}
And you can call it like
<div id="divID" onclick="tap(tapOnce, tapTwice)" >
tapOnce and tapTwice are your functions which will be called in respective cases. This solution will work on browsers too.
Reference
Here is the external function 'doubletap' which can be helpful:
/*
* jQuery Double Tap
* Developer: Sergey Margaritov (sergey#margaritov.net)
* Date: 22.10.2013
* Based on jquery documentation http://learn.jquery.com/events/event-extensions/
*/
(function($){
$.event.special.doubletap = {
bindType: 'touchend',
delegateType: 'touchend',
handle: function(event) {
var handleObj = event.handleObj,
targetData = jQuery.data(event.target),
now = new Date().getTime(),
delta = targetData.lastTouch ? now - targetData.lastTouch : 0,
delay = delay == null ? 300 : delay;
if (delta < delay && delta > 30) {
targetData.lastTouch = null;
event.type = handleObj.origType;
['clientX', 'clientY', 'pageX', 'pageY'].forEach(function(property) {
event[property] = event.originalEvent.changedTouches[0][property];
})
// let jQuery handle the triggering of "doubletap" event handlers
handleObj.handler.apply(this, arguments);
} else {
targetData.lastTouch = now;
}
}
};
})(jQuery);
Load jQuery Mobile into your project and try using taphold or some of the other mobile specific touch events that are available to you through that API.
Here's the jQuery Mobile documentation with all the events you can use: http://api.jquerymobile.com/category/events/
Here is the snippet for TS React users. Pass in the click event, so that double click is only invoked if the same element is clicked twice
import React from "react";
type CallBack = () => any;
type TapParams = { onSingleTap?: CallBack; onDoubleTap?: CallBack };
var DELTA_TIME_THRESHOLD_MS = 700;
var timer: NodeJS.Timeout | null = null;
var target: EventTarget;
export function tap(
e: React.MouseEvent,
{ onSingleTap, onDoubleTap }: TapParams
) {
if (timer == null) {
// First tap
onSingleTap?.();
timer = setTimeout(() => {
timer = null;
}, DELTA_TIME_THRESHOLD_MS);
} else {
// Second tap
if (e.target === target) {
onDoubleTap?.();
}
clearTimeout(timer);
timer = null;
}
target = e.target;
}
Usage
<div
onClick={(e) => tap(e, { onSingleTap, onDoubleTap })}
>Tap or doubletap</div>
Using only JavaScript
You can use "touchstart" event for a single touch,
but with calculating the time when he should click again
I used 400 (0.4s) as it's the longer duration between two touches
It's only an estimate, but it's still a reasonable time
let expired
let doubleClick = function () {
console.log('double click')
}
let doubleTouch = function (e) {
if (e.touches.length === 1) {
if (!expired) {
expired = e.timeStamp + 400
} else if (e.timeStamp <= expired) {
// remove the default of this event ( Zoom )
e.preventDefault()
doubleClick()
// then reset the variable for other "double Touches" event
expired = null
} else {
// if the second touch was expired, make it as it's the first
expired = e.timeStamp + 400
}
}
}
let element = document.getElementById('btn')
element.addEventListener('touchstart', doubleTouch)
element.addEventListener('dblclick', doubleClick)
In case of this error :
Unable to preventDefault inside passive event listener due to target being treated as passive.
event.preventDefault( ) not working if element = "document" or "document.body"
So the solution of that, you should have a full page div container :
let element = document.getElementById('container')
element.style.minWidth = '100vw'
element.style.minHeight = '100vh'
document.body.style.margin = '0px'
element.addEventListener('touchstart', elementTouch)
element.addEventListener('dblclick', doubleClick)

AngularJS How to restrict double/multiple click

I know a solution for double/multi click on angularJS that disable on button click & enable it after completing ajax processing using ng-disabled. Please suggest any other best solution for handling double click in angularJS...
Our code will call ajax method on button click & will take small amount of tme to process & get data from db. We have to restrict second/ multi clicks mean while.
I don't want to allow click when ajax in progress...
Welcome Please be simple solutions rather than using ng-disabled, I am learner of AngularJS
createDialogService('ajs/common/templates/popup/config_reminder_popup.html',
{
title: isFrom,
backdrop: true,
controller: 'configRemindersCBController',
footerTemplate: '' + action + ''
});
$scope.saveOrUpdateReminder = function (reminder)
{
if ($scope.isDisabled)
{
return;
}
$scope.isDisabled = true;
if (!reminder.daysBeforeAfterCheckDate || reminder.daysBeforeAfterCheckDate === '')
{
alertService.openValidatPopup('Please enter days before expiration.', "Error", true, 'configRemindersCBController', 'Ok', 'u1_remove.png');
$scope.isDisabled = false;
return;
}
configRemindersService.isDaysBeforeAfterCheckDate($scope.objectId, reminder, function (result)
{
if (!reminder.selectedMessageTemplate.messageId || reminder.selectedMessageTemplate.messageId === '')
{
alertService.openValidatPopup('Please select message template.', "Error", true, 'configRemindersCBController', 'Ok', 'u1_remove.png');
$scope.isDisabled = false;
return;
}
else if (!reminder.selectedReminderSendOption.reminderSendOptionValue || reminder.selectedReminderSendOption.reminderSendOptionValue === '')
{
alertService.openValidatPopup('Please select reminder send option.', "Error", true, 'configRemindersCBController', 'Ok', 'u1_remove.png');
$scope.isDisabled = false;
return;
}
var enableReminder;
if (result.Result === 'No')
{
if (reminder.enable === true)
{
enableReminder = 'Enable';
}
else
{
enableReminder = 'Disable';
}
configRemindersService.addOrUpdateReminderConfigLine($scope.objectId, reminder, enableReminder, function (remindersResponse)
{
var reminder = remindersResponse.reminderConfigLine;
$rootScope.CONFIG = JSON.parse(remindersResponse.configData);
$scope.$modalClose();
$scope.isDisabled = false;
_.filter(configRemindersService.getMessageTemplates(), function (msg)
{
if (reminder.messageTemplateId === msg.messageId)
{
reminder.selectedMessageTemplate = msg;
}
});
_.filter(configRemindersService.getReminderSendOptions(), function (option)
{
if (reminder.reminderSendOption === option.reminderSendOptionValue)
{
reminder.selectedReminderSendOption = option;
}
});
if (configRemindersService.getIsFrom() === 'Create Reminder')
{
configRemindersService.getReminders().push(reminder);
}
else
{
configRemindersService.getReminders()[configRemindersService.getIndex()] = reminder;
}
});
}
});
};
ng-disabled is definitely the proper way to go with this -- it's a ready-made directive for disabling an object under certain conditions.
If you need to disable it without it looking disabled, you have 2 options:
Change the css for the disabled state to look like it's enabled.
Mimic this with a scope variable that only performs the actions when set.
For the second:
$scope.stopClick = false;
$scope.buttonClick = function () {
if ($scope.stopClick) { return; }
$scope.stopClick = true;
<.. code ..>
callback: function(data) {
$scope.stopClick = false;
}
};
This will accomplish the goal, but it's reinventing the wheel and is likely less robust than just disabling the element and restyling it.
Well you could do something like this :
Create a $scope variable
$scope.myFunnyPromise = null;
in the template :
<button ng-if="myFunnyPromise == null"></button>
Only show the button if myFunnyPromise equals null.
And in your function :
if($scope.myFunnyPromise !== null){
return $scope.myFunnyPromise;
}
$scope.myFunnyPromise = asyncTask().finally(function cleanUp(){
//clear the $scope variable
$scope.myFunnyPromise = null;
})
You can have a function in $rootScope which contains information about whether there is an ongoing http request. Then you can use that value to disable the buttons that you don't want successive click events.
I usually use fieldset attribute to disable input fields while there is an ongoing http request.
<fieldset ng-disabled="isWaitingForServerResponse()"> //when there is an ongoing request, input fields under this tag will be disabled </fieldset>
For implementing isWaitingForServerResponse I got help from a busy bar implementation, which shows a loading bar while there is a http request. It creates events when there is a new request and another request when it stops. So in these events I incremented a counter which holds the number of active http requests and decrement it for each http response. I haven't used it before but I guess you can also use the $http.pendingRequests property too for understanding whether there is a pending http request.
$rootScope.numberOfResponseWaitingFromServer = 0;
$rootScope.$on("cfpLoadingBar:loading", function (event) {
$rootScope.numberOfResponseWaitingFromServer++;
});
$rootScope.$on("cfpLoadingBar:loaded", function (event) {
$rootScope.numberOfResponseWaitingFromServer--;
});
$rootScope.isWaitingForServerResponse = function () {
return $rootScope.numberOfResponseWaitingFromServer > 0;
}

implementing a callback in TimelineJS

In TimelineJS I want to implement a callback that would be fired every time the slide (the main panel in the center) changes whether that change happens by clicking on the "right" or "left" buttons on the sides, or by clicking on an event in the timeline in the bottom time panel. I could not find any information on such a callback other than a reference to it in Issue 82 from a year ago.
Suggestions on how to start with this? (In other words, I have no idea even where to begin).
Timeline JS fires an event whenever it is updated, the code is at line 5274 of the latest version:
function upDate() {
config.current_slide = current_slide;
VMM.fireEvent(layout, "UPDATE");
};
The code for fireEvent is:
VMM.fireEvent = function(element, the_event_type, the_data) {
var e;
var _event_type = "click";
var _data = [];
if (the_event_type != null && the_event_type != "") {
_event_type = the_event_type;
}
if (the_data != null && the_data != "") {
_data = the_data;
}
if( typeof( jQuery ) != 'undefined' ){
jQuery(element).trigger(_event_type, _data);
//return e;
}
};
Then important part there is:
jQuery(element).trigger(_event_type, _data);
So, all you need to do is figure out what element layout is, so I would temporatily edit the upDate function to:
function upDate() {
console.log($(layout).attr('id') + ' : ' + $(layout).attr('class'));
config.current_slide = current_slide;
VMM.fireEvent(layout, "UPDATE");
};
And see what it logs for the class / id of the element. Then, say it comes back with the class being 'timeline', for example, you would write something like this:
$('.timeline').on("UPDATE", function() {
your_function_that_does_whatever_you_need();
});

Categories

Resources