how to destroy or block mount events in mounted() vuejs - javascript

I'm making a wrapper component so I need to add all the events in mounted() methods. However the thing is, as it's another component, whenever I open that component, event is triggered. I'm not sure how to block it. Even I made it to be triggered when the component is clicked, but it didn't work. It only works for the first mount. After re-open it(from second mount), it just keep triggers all the event and I have to block it.
Is there a way that I can block to not to trigger events in mounted() hook for vuejs?
EDITED:
I'm making leaflet-draw wrapper. all the events are from leaflet-draw doc.
this.addnew() is the one being triggered.
objectLayer.on("layeradd", (e) => {
let layer = e.layer;
layer.on("click", onClickFeatureSelct, layer);
if (typeof layer.options.id === "undefined") {
layer.options.id = L.Util.stamp(layer);
}
if (!layer.feature) {
let json = layer.toGeoJSON();
layer.feature = L.GeoJSON.asFeature(json);
}
let properties = layer.feature.properties;
let keyvalue = L.stamp(layer);
if (layer instanceof L.NodeCircle) {
let latlng = layer.getLatLng();
itemType = "node";
let nodes = this.$store.getters.nodeList;
let result = false;
for (let i = 0; i < nodes.length; i++) {
if (nodes[i].keyvalue == keyvalue) {
result = true;
} else {
result = false;
}
}
if (!result) {
console.log('layer added')
// this.addNew(latlng, itemType, keyvalue);
}
if (!properties.NODE_ID) {
properties.NODE_ID = parseInt(this.newNodeId);
properties.NODE_NAME = "-";
this.addedNodes.push(properties.NODE_ID);
layer.bindTooltip(properties.NODE_NAME + "<br>(" + properties.NODE_ID.toString() + ")");
nodeObj[keyvalue.toString()] = layer;
}
// console.log('added nodes', this.addedNodes)
if (!nodeLayer.hasLayer(layer)) nodeLayer.addLayer(layer);
}
});

Well, As this question got 5 ups, to people who's facing same issue just like me. Here is How I did...
Vue.js mount order when components are related.
Child Component -> Parent Component
Adding this.$nextTick() didn't work.
Even it's a SPA Web application. There is no way to NOT to trigger events when they're in the child component. So I just made it to reload..... I know it's not a good idea to do it but I couldn't find the any other way to fix it. However, I think adding flags to parent component and trigger that event when parent is ready might gonna work.
I will re-try this logic once again and let you know how I've done afterwards. It won't be that soon. Sorry.

Related

Is there a way I can dynamically bind a string and the text it outputs without using setInterval?

Is there a way I can dynamically bind a string and the text it outputs without using setInterval? I want it to be similar to Angular and Vue though I want to do this with vanilla JS. I want to be able to open the console and change the value at any time and see the change output on my element. Thank you in advance!
I think your only two options are:
A. Edit the element directly, e.g.
myPublicElemeVariable.innerText = 'Bla'
B. Use a setter (or Proxy):
obj = {
get str() { return this.myStr; }
set str(val) {
elem.innerText = val;
this.myStr = val;
}
}
C. Just use a function/method!
If you mean you want change to be event-driven, there is already a very simple event framework in javascript - the EventTarget class as demonstrated by this Code Sandbox
//define a watchable thing
class ValueTarget extends EventTarget {
constructor(value = "") {
super();
this.setValue(value);
}
getValue() {
return this._value;
}
setValue(value) {
this._value = value;
this.dispatchEvent(new CustomEvent("change", { detail: { value } }));
}
}
//get the page elements
const inputElement = document.querySelector("input");
const outputElement = document.querySelector("h1");
//create a watchable thing
const fieldTarget = new ValueTarget("");
//wire events that will change the watchable
inputElement.addEventListener("input", function (e) {
fieldTarget.setValue(e.target.value);
});
//receive notifications from the watchable
fieldTarget.addEventListener("change", (e) => {
outputElement.textContent = e.detail.value;
});
You may be as well to build your own given how simple it is - maintains a list of listeners and calls them when notified. My work recently needed such a thing which I knocked up in Typescript at https://github.com/cefn/lauf/blob/main/modules/lauf-store/src/core/watchable.ts#L3-L21 and would therefore be very easy to redo in javascript.

Get element event listener and store it

I want to remove an event listener from an element and later assign it back. Is there a way to get it and store in a variable smth like var storedListener = Element.getEventListener('click'), so that later I can do something like Element.addEventListener('click', storedListener)?
UPDATE
Listener is assigned inside template, I use Angular 2. It's
<div *ngFor="let entry of data; let i=index">
<div class="element-description" (click)="editElementDescription(i)">{{entry.description}}</div>
</div>
What I want to do is to make contents of inside <div> an <input> after I click it, so that I can change value and send it to server. And inside editElementDescription() I do the following:
public editElementDescription(index: number): void {
var elementDescription: HTMLDivElement = <HTMLDivElement>document.getElementsByClassName('element-description')[index];
elementDescription.removeEventListener('click');
elementDescription.innerHTML = '<input id="change-description-input" type="text" value="' + elementDescription.innerHTML + '"/>';
}
I remove that click listener because otherwise contents of <input> will get that innerHTML if I click it one more time. So the idea is to assign that <input> element a change listener, which will replace <input> by it's value and bring the parent <div> it's original listener back.
In order to remove a listener added with .addEventListener(), you must keep track of the listener function reference and remove it later with .removeEventListener().
Something like that:
var btn = document.getElementById('btn');
var btn_add = document.getElementById('btn-add-listener');
var btn_remove = document.getElementById('btn-remove-listener');
var fnListener = function(e) {
alert('Clicked!');
};
btn_add.addEventListener('click', function() {
btn.addEventListener('click', fnListener);
});
btn_remove.addEventListener('click', function() {
btn.removeEventListener('click', fnListener);
});
Working demo: https://jsfiddle.net/mrlew/k5m1nog3/
Other approach (added after question update)
Considering your actual problem, I suggest another approach: instead of handle events, you can set a data- attribute in the element indicating it's open. Then you just modify your inner HTML if the attribute is not present.
Something like this:
function editElementDescription(index) {
var elementDescription = document.getElementsByClassName('element-description')[index];
var isOpen = elementDescription.getAttribute('data-isOpen');
if (!isOpen) {
elementDescription.setAttribute('data-isOpen', 'true');
elementDescription.innerHTML = '<input id="change-description-input" type="text" value="' + elementDescription.innerHTML + '"/>';
}
}
Working demo: https://jsfiddle.net/mrlew/e0yrL08v/
You are getting it a bit wrong... there is no list of event listeners accessible from JavaScript... the only thing you can do is to remove/add an event if you know the origin.
There are 2 functions to manipulate event listeners:
addEventListener(event_type,function);
removeEventListener(event_type,function);
One object can have multiple events of same type... the only way to distinguish them is by giving an exact function being called.
Please note that if it's jQuery, it is possible, as it has own event stack... example below:
var events = $("#object1").data('events');
var $object2 = $("#object2");
if (events) {
for(var eventType in events){
for(var idx in events[eventType]){
$object2[eventType](events[eventType][idx].handler);
}
$('#object1').off(eventType);
}
}
No, this isn't possible since .getEventListener is only available for debugging purposes.
There is unfortunately no way in standard JavaScript to programmatically get back the EventListeners attached to an object, and any library that tries to accomplish this will rely on unstable non-standard interfaces that may be discontinued any day.
So if your goal was to manipulate the listeners added by a library you have no control over, you're out of luck.
On the other hand if you control the environment then you can store a reference to the attached callback if you want to attach the same listener to multiple objects, or remove it afterwards with .removeEventListener.
You could actually monkey-patch EventTarget.prototype.addEventListener to support this before anything else runs on your page, that wouldn't be a very clean solution to whatever problem you are having but if you think you really need to, here is a quick imperfect implementation of it (doesn't support useCapture argument):
// getEventListener polyfill, run this before anything else on your page.
(function monkeyPatchGetEventListeners(EventTarget) {
const eventListeners = new WeakMap();
const origAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function patchedAddEventListener(eventType, listener, ...args) {
let allListeners;
if (eventListeners.has(this)) {
allListeners = eventListeners.get(this);
} else {
allListeners = new Map();
eventListeners.set(this, allListeners);
}
let listeners;
if (allListeners.has(eventType)) {
listeners = allListeners.get(eventType);
} else {
listeners = [];
allListeners.set(eventType, listeners);
}
listeners.push(listener);
return origAddEventListener.call(this, eventType, listener,...args);
}
const origRemoveEventListener = EventTarget.prototype.removeEventListener;
EventTarget.prototype.removeEventListener = function patchedRemoveEventListener(eventType, listener, ...args) {
const call = () => origRemoveEventListener(eventType, listener, useCapture, ...args);
const allListeners = eventListeners.get(this);
if (!allListeners) { return call(); }
const listeners = allListeners.get(this);
if (!listeners) { return call(); }
const index = listeners.indexOf(listener);
if (index === -1) { return call(); }
index.splice(index, 1);
return call();
}
EventTarget.prototype.getEventListeners = function patchedGetEventListeners(eventType) {
const allListeners = eventListeners.get(this);
return allListeners && allListeners.get(eventType);
}
})(EventTarget);

Flux/Alt setTimeout not updating store

I'm trying to create a basic "Toast" like service in my React app using Alt.
I've got most of the logic working, I can add new items to the array which appear on my view when triggering the add(options) action, however I'm trying to also allow a timeout to be sent and remove a toast item after it's up:
onAdd(options) {
this.toasts.push(options);
const key = this.toasts.length - 1;
if (options.timeout) {
options.timeout = window.setTimeout(() => {
this.toasts.splice(key, 1);
}, options.timeout);
}
}
On add, the toast appears on my page, and the timeout also gets triggered (say after a couple of seconds), however manipulating this.toasts inside of this setTimeout does not seem to have any effect.
Obviously this is missing the core functionality, but everything works apart from the setTimeout section.
It seems that the timeout is setting the state internally and is not broadcasting a change event. It might be as simple as calling forceUpdate(). But the pattern I use is to call setState() which is what I think you might want in this case.
Here is an example updating state and broadcasting the change event.
import alt from '../alt'
import React from 'react/addons'
import ToastActions from '../actions/ToastActions'
class ToastStore {
constructor() {
this.toasts = [];
this.bindAction(ToastActions.ADD, this.add);
this.bindAction(ToastActions.REMOVE, this.remove);
}
add(options) {
this.toasts.push(options);
this.setState({toasts: this.toasts});
if (options.timeout) {
// queue the removal of this options
ToastActions.remove.defer(options);
}
}
remove(options) {
const removeOptions = () => {
const toasts = this.toasts.filter(t => t !== options);
this.setState({toasts: toasts});
};
if (options.timeout) {
setTimeout(removeOptions, options.timeout);
} else {
removeOptions();
}
}
}
module.exports = alt.createStore(ToastStore, 'ToastStore');

Sub-viewmodels in Knockoutjs

Well met!
I am playing around with Knockoutjs with the goal of having a single ViewModel, which controls multiple sub-viewmodels. This in order to have more control over the views itself and to prevent putting various parts of my view into their own little place. The code below should explain my idea:
ApplicationViewModel
ApplicationViewModel = function () {
var self = this;
// Context (for laziness' sake, no separate VM)
self.activeProject = ko.observable();
// States
self.projectsLoaded = ko.observable(false);
// State-change events
// Let application know that loading of projects has been called
self.projectsLoaded.subscribe(function (newValue) {
if (newValue === true) {
console.log('Projects have loaded');
} else {
console.log('Projects have not loaded');
}
});
// Let application know that selection of a project has happened
self.activeProject.subscribe(function (newValue) {
if (newValue != null) {
// Notify other viewmodels that a project has been (successfully loaded)
// Use hook-pattern to hook into this event
} else {
// Notify something went wrong- present user with a notification
// Application stops processes that are project-dependant
}
});
self.ProjectViewModel = new ProjectViewModel();
};
ProjectViewModel
ProjectViewModel = function () {
var self = this;
self.projects = ko.observableArray();
self.loadProjects = function () {
// Business logic to retrieve projects, think AJAX
var placeHolderProjects = [];
// Find projects somewhere and load them up!
// If something went wrong, notify parent
if (placeHolderProjects.length > 0) {
self.projects(placeHolderProjects);
$root.projectsLoaded(true);
} else {
$root.projectsLoaded(false);
}
};
self.selectProject = function (projectId) {
if (!projectId) {
$.parent.activeProject = null;
return;
}
// Fetch data for project, stuff like membershipId
var loadProjectResult = magicalLoadFunction(projectId);
if (loadProjectsResult === true) {
$root.activeProject(projectId);
} else {
$root.activeProject(projectId);
}
// Exit
return;
}
/********** Constructor logic
****************************/
self.loadProjects();
};
So basically, what I am looking for, is a way to:
- Control parent/child properties from their respective child/parent inside the viewmodels.
I am looking into AngularJS as well, but I'd really like to get this working in KnockoutJS first :) Immediate problem, is that I can't get $root/$parent to work. I bind the ApplicationViewModel in a $(document).ready() handler, unsure if I have to actually bind the sub-viewmodels to the view as well. I have bound ApplicationViewModel to the body element.
Thanks for reading and, possibly for answering/helping me get on my way :)
The answer provided by #jansommer proved successful.
I changed the following line (added this as a parameter):
self.ProjectViewModel = new ProjectViewModel(this);
And that was what was needed.
Thanks!

jQuery: Why would trigger not fire from a JS object?

I've been implementing a form of a publisher/subscriber design pattern in jQuery. I'm basically building classes in Javascript utilizing CoffeeScript that serve as components on my page. i.e. Navigation, DataList, etc.
Instead of having DOM elements fire events, I have instances of these classes that use trigger on themselves to send custom events. These instances can then listen to each other and can update the DOM elements they own accordingly based on the changes in each others behavior!
I know this works as I have one of my components dispatching a custom event properly. However, I've ran into a snag. I've created another component and for the life of me I cannot figure out why it's event is not being fired.
This is the implementation of my class:
window.List = (function() {
List = function(element, settings) {
var _a, _b, _c;
this.list = $(element);
this.settings = jQuery.extend(List.DEFAULTS, settings);
this.links = this.list.find(this.settings.link_selector);
this.links.selectable();
_b = [SelectableEvent.COMPLETED, SelectableEvent.UNDONE, SelectableEvent.SELECTED, SelectableEvent.DESELECTED];
for (_a = 0, _c = _b.length; _a < _c; _a++) {
(function() {
var event_type = _b[_a];
return this.links.bind(event_type, __bind(function(event, selectable_event) {
return this.dispatch(selectable_event);
}, this));
}).call(this);
}
return this;
};
List.DEFAULTS = {
link_selector: "a",
completed_selector: ".completed"
};
List.prototype.change = function(mode, previous_mode) {
if (mode !== this.mode) {
this.mode = mode;
if (previous_mode) {
this.list.removeClass(previous_mode);
}
return this.list.addClass(this.mode);
}
};
List.prototype.length = function() {
return this.links.length;
};
List.prototype.remaining = function() {
return this.length() - this.list.find(this.settings.completed_selector).length;
};
List.prototype.dispatch = function(selectable_event) {
$(this).trigger(selectable_event.type, selectable_event);
return alert(selectable_event.type);
};
return List;
}).call(this);
Pay attention to:
List.prototype.dispatch = function(selectable_event) {
$(this).trigger(selectable_event.type, selectable_event);
return alert(selectable_event.type);
};
This code is triggered properly and returns the expected event type via an alert. But before the alert it is expected to trigger a custom event on itself. This is where I'm encountering my problem.
$(document).ready(function() {
var list_change_handler, todo_list;
todo_list = new List("ul.tasks");
list_change_handler = function(event, selectable_event) {
return alert("Hurray!");
};
$(todo_list).bind(SelectableEvent.COMPLETED, list_change_handler);
$(todo_list).bind(SelectableEvent.UNDONE, list_change_handler);
$(todo_list).bind(SelectableEvent.SELECTED, list_change_handler);
$(todo_list).bind(SelectableEvent.DESELECTED, list_change_handler);
}
You see here the alert "Hurray" is what I want to fire but unfortunately I am having no luck here. Ironically I've done the exact same thing with another class implemented the same way dispatching a custom event and the listener is receiving it just fine. Any ideas on why this wouldn't work?
Update:
Per discussing in the comments, it looks like Logging "this" in console returns the JS Object representing the class. But logging "$(this)" returns an empty jQuery object, thus trigger would never be fired. Any thoughts on why $(this) is coming up empty when "this" is accurately returning the instance of the class?
I found out that jQuery could not index my object because the class implemented it's own version of a jQuery method. In this case, length(). Renaming the length() method to total() resolved the problem completely and any instance of the class can successfully trigger events.

Categories