dynamic import's on.("click", ...) can't find global variable - javascript

I am trying to create an application in electron (code in front end javascript side) to aid in my writing, and something I need is to dynamically load modules/plugins/mods. I am able to actually import a function that has a class returned from it (It was the only way so far that I could actually get dynamic import of a class to work...), and it assigns some jquery node.on("click") that do not run as expected. I have several object containers that I use to separate major portions of my code from any by chance matching variables. Anyways, the one for menus (MenuManager) throws an error that it cannot be found when I click the element for on("click").
I want the variables like MenuManager to be globally available inside the app, so plugins/mods can make any decided changes.
MenuManager.js
//Isolate MenuManager variables to itself, to majorly reduce variable naming mishaps and make it
//easier to modify something later
let MenuManager = {
menus: []
}
//import PluginDef from "../../Workspaces/Writing/define.js";
$(function () {
console.log("MenuManager Booting!");
//Create menus from plugin
LoadPlugins();
LoadPlugins();
});
function LoadPlugins() {
/*const testLoad = import("../../Workspaces/Writing/define.js");
console.log(testLoad.default.PluginDef().Name);*/
import("../../Workspaces/Writing/define.js").then((module) => {
let newModule = module.default();
newModule.Generate();
MenuManager.menus.push(newModule);
console.log("loading: " + MenuManager.menus[0].Name);
}).catch((err) => {
console.log(err);
});
}
imported define.js
export default function PluginDef() {
class def {
constructor() {
this.Name = "Writing";
this.MenuBase = "<h5>Menus HTML here</5>";//import from file later
this.workspaceBase = "<h5>Workspace HTML here</h5>";//import from file later
}
Generate() {
this.GenerateSelector();
this.GenerateMenu();
this.GenerateWorkspace();
this.Run_OnGenerated();
}
GenerateSelector() {
let Self = this;
//Generate Menu Selector Header
this.menuSelector = $([
"<li>",
this.Name,
"</li>"
].join("\n"));
//Add listener to selector
this.menuSelector.on("click", function () {
//let foundMenu = MenuManager.menus.find(x => x.Name == this.textContent);
MenuManager.currentMenu.menu.removeClass("selectedMenu");
MenuManager.currentMenu.workspace.removeClass("selectedWorkPanel");
MenuManager.currentMenu.Run_OnClose();//Ran whenever menu is deselected
MenuManager.currentMenu = Self;//Set 'this' to currently selected menu
MenuManager.currentMenu.menu.addClass("selectedMenu");
MenuManager.currentMenu.workspace.addClass("selectedWorkPanel");
MenuManager.currentMenu.Run_OnOpen();//Ran whenever menu is selected
});
//Add selector to header
AppPart.MenuBarSelector.find("ul").append(this.menuSelector);
}
GenerateMenu() {
//Generate Menu
this.menu = $([
"<div id='Menu_Writing' class='menuBarPanel'>",
this.MenuBase,
"</div>"
].join("\n"));
//Add menu to panels
AppPart.MenuBarOptionsContainer.append(this.menu);
}
GenerateWorkspace() {
//Generate Workspace
this.workspace = $([
"<div id='WorkPanel_Writing' class='workPanel'>",
this.workspaceBase,
"</div>"
].join("\n"));
//Add workspace to workspace
AppPart.WorkAreaContainer.append(this.workspace);
}
//When menu is generated, run this function
Run_OnGenerated() {
return;//does nothing
}
//When menu is selected, run this function
Run_OnOpen() {
return;//Does Nothing
}
//When menu is deselected, run this function
Run_OnClose() {
return;//Does Nothing
}
}
return new def();
}
GenerateSelector() of imported define.js (line 27 specifically) is where the error for MenuManager is not defined happens

I did not see #Chris G's comment/answer, because I did not get an email or such. But anyways, I had figured out through I think blind luck that:
I could create a base class and place that in window, where the imported class could easily access the class without trying to remember or know any file locations (And yes, I am working on keeping global/window clean, not shoving it with tons of variables, just the few important ones like this case)
Note: I cannot explain why this exactly works, but possibly due to the scope.
MenuManager.js
function LoadPlugins() {
import("../../Workspaces/Writing/define.js").then((module) => {
let newModule = module.default();
MenuManager.menus.push(newModule);
console.log("loaded: " + MenuManager.menus[0].Name);
}).catch((err) => {
console.log(err);
});
}
class PluginBaseDef {
Name;
MenuBase;
workspaceBase;
menuSelector;
menu;
workspace;
constructor() {
}
Generate() {
this.GenerateSelector();
this.GenerateMenu();
this.GenerateWorkspace();
this.Run_OnGenerated();
if (!MenuManager.currentMenu.Name) {
this.ShowMenu();
}
}
}
window.PluginBaseDef = PluginBaseDef;
imported define.js
export default function PluginDef() {
class def extends window.PluginBaseDef {
constructor() {
super();
this.Name = "Writing";
this.MenuBase = "<h5>Menus HTML here</5>";//import from file later
this.workspaceBase = "<h5>Workspace HTML here</h5>";//import from file later
this.Generate();
}
}
return new def();
}

Related

Calling static method from different class doesn't work without using default

I've just finished a lengthy debugging session and while I solved the issue, I have no idea why my solution is necessary.
I have a file Update.js that looks like this:
class Update {
static doThisAfterClick = (event) => {
console.log("First button clicked!");
}
static doThisWhenCalled = (name, age) => {
console.log("My name is " + name + " and my age is " + age);
}
}
export default Update;
In Edit.js I import the module:
let Update = require('./Update');
class Edit {
static fancyEventHandler = (event) => {
Update.doThisWhenCalled("Peter", 42);
}
}
export default Edit;
and later, in app.js I add a few Event handlers:
import Edit from "./Edit";
import Update from "./Update";
firstButton.addEventListener('click', Update.doThisAfterClick);
secondButton.addEventListener('click', Edit.fancyEventHandler);
Now the crazy part: Clicking firstButton logged First button clicked! as expected, but clicking secondButton gave an error: Uncaught TypeError: Update.doThisWhenCalled is not a function..
I actually tried to debug this by inserting
let x = Update.doThisWhenCalled;
debugger;
into fancyEventHandler() and saw that x was undefined.
Through some sheer luck I saw that I could use
Update.default.doThisWhenCalled("Peter", 42);
instead and this resolved the issue. No idea why.
What is wrong with calling the static method directly? And what is this "default" property?
I think it's related to the way you're exporting/importing, below is a possible fix:
Update.js:
class Update {
static doThisAfterClick() {
console.log("First button clicked!");
}
static doThisWhenCalled(name, age) {
console.log("My name is " + name + " and my age is " + age);
}
}
export default Update;
Edit.js:
import Update from "./Update";
class Edit {
static fancyEventHandler() {
Update.doThisWhenCalled("Peter", 42);
}
}
export default Edit;

Function sortAlpha is undefined

I am currently trying to sort all my game objects alphabetically by title. I have gotten my onclick to register but it does not execute my JS function below is the HTML and JS snippets. The sortAlpha is a method within the Games class.
*edit 1: Adjusted function to attach a event listener
*edit 2: I have opted to create a variable to store and call the function. My next question is am I not correctly displaying the newly alphabetized contents with my function ? I get no errors and my log shows that clicks are being registered.
<div id="filter">
<div id="buttons">
<button onclick="let the_game = new Games(); the_game.sortAlpha()">Sort Games Alphabetically</button>
</div>
</div>
class Games {
constructor() {
this.games = []
this.adapter = new GamesAdapter()
this.initBindingsAndEventListeners()
this.fetchAndLoadGames()
}
initBindingsAndEventListeners() {
this.newGameForm = document.getElementById('new-game-form')
this.newGameTitle = document.getElementById('new-game-title')
this.newGameDeveloper = document.getElementById('new-game-developer')
this.newGameCover = document.getElementById('new-game-cover')
this.newGameForm.addEventListener('submit', this.createGame.bind(this))
}
createGame(g) {
g.preventDefault();
const titleValue = this.newGameTitle.value;
const developerValue = this.newGameDeveloper.value;
const coverValue = this.newGameCover.value;
this.adapter.createGame(titleValue, developerValue, coverValue)
.then(game => {
const newGame = new Game(game)
this.games.push(newGame)
this.newGameTitle.value = ' '
this.newGameDeveloper.value = ' '
this.newGameCover.value = ' '
newGame.renderGameBlock()
})
}
fetchAndLoadGames() {
this.adapter
.getGames()
.then(games => {
games.forEach(game => this.games.push(new Game(game)))
})
.then(() => {
this.renderGames()
})
}
renderGames() {
this.games.map(game => game.renderGameBlock())
}
sortAlpha() {
console.log('test');
this.games.sort(function(gameA, gameB){
if (gameA.title < gameB.title) {
return -1;
}
if (gameA.title > gameB.title){
return 1;
}
return 0;
});
window.onload = function() {
document.getElementById("filter").onclick = sortAlpha;
}
}
}
The sortAlpha is a method within the Games class.
That's your answer right there. sortAlpha is not a free-function, but your onclick="" attribute references sortAlpha() as though it were a free-function.
(A "free function" is a function that is not a member of a class/type)
You need to move your sortAlpha function to the global scope, or attach it to the button using addEventListener. You cannot use class method functions in onclick="" without fully qualifying them.

How to fix problem with not working updateServing function in JavaScript?

I'm trying to implement a function which would calculate the servings for the ingredients from my website.
That function is in Recipe.js file and looks like that:
updateServings(type) {
// Servings
const newServings = type === 'dec' ? this.servings - 1 : this.servings + 1;
// Ingredients
this.ingredients.forEach((ingr) => {
ingr.count = this.capDecimal(ingr.count * (newServings / this.servings));
});
this.servings = newServings;
}
The problem is that when I console.log(state.recipe); in index.js this event Listener works, it will console log state.recipe after clicking - or + button on the website but it wont change the amount of serving in the recipe object:
elements.recipe.addEventListener('click', e => {
if(e.target.matches('.btn-decrease .btn-decrease *')){
//Decrease button is clicked
if(state.recipe.servings > 1){
state.recipe.updateServings('dec');
}
}else if(e.target.matches('.btn-increase .btn-increase *')){
//Increase button was clicked
state.recipe.updateServings('inc');
}
console.log(state.recipe);
});
I clicked 2 times but property serving still says 4 like here:
https://forum.toshitimes.com/uploads/toshitimes/original/2X/6/6bada9081879db1a14df9bad010382606fda253f.png
It a bigger project so I believe I need to include the whole repository from github: https://github.com/damianjnc/forkifyApp.git
What I need to change to make it work?
You need to update the view after the click event
elements.recipe.addEventListener('click', e => {
//....
try {
recipeView.clearRecipe();
recipeView.renderRecipe(state.recipe);
} catch (error) {
alert('error processing the recipe:(');
}
});
note: you need to declare your class properties
export default class Recipe {
ingredients;
servings;
constructor(id) {
this.id = id;
}
and you need map instead of forEach
this.ingredients = this.ingredients.map((ingr) => {
ingr.count = this.capDecimal(ingr.count * (newServings / this.servings));
return ingr;
});

PrimeNg TabView with ConfirmDialog

I'm trying to use PrimeNg TabView component along with confirmDialog unsuccessfully, here is my code:
<p-tabView (onChange)="onTabChange($event)" [(activeIndex)]="index">...</p-tabView>
onTabChange(event){
this.confirmationService.confirm({
message: 'Do you confirm ?',
accept: () => {
this.index = event.index;
},
reject:() =>{ }
});
}
Do you have an idea on how to prevent or allow tab change using confirm dialog ?
Thanks
Based on similar solution for material design tabs, here is the solution for my issue:
in html Declare a local variable referencing TabView DOM object:
<p-tabView #onglets>...</p-tabView>
in component.ts, change default function called when click on tab with specific
function to match your case:
#ViewChild('onglets') onglets: TabView;
this.onglets.open = this.interceptOngletChange.bind(this);
...
interceptOngletChange(event: Event, tab: TabPanel){
const result = confirm(Do you really want to leave the tab?);
return result && TabView.prototype.open.apply(this.onglets, argumentsList);
});
}
I had similar problem. Needed show dialog before tab change.
My solution:
HTML
<p-tabView #tabView (onChange)="onChange($event)" />
TS
#ViewChild('tabView') tabView: TabView;
onChange(event: any) {
const previoustab = this.tabView.tabs[this.prevIndex]; //saved previous/initial index
previoustab.selected = true;
const selectedTab = this.tabView.tabs[event.index];
selectedTab.selected = false;
this.tabView.activeIndex = this.prevIndex;
this.nextIndex= event.index;
}
GoToNextTab() {
this.tabView.activeIndex = this.nextIndex;
this.prevIndex= this.nextIndex;
this.tabView.open(undefined, this.tabView.tabs[this.nextIndex]);
}
With this code you will stay on the selected tab without tab style changes.

jQuery plugin instances variable with event handlers

I am writing my first jQuery plugin which is a tree browser. It shall first show the top level elements and on click go deeper and show (depending on level) the children in a different way.
I got this up and running already. But now I want to implement a "back" functionality and for this I need to store an array of clicked elements for each instance of the tree browser (if multiple are on the page).
I know that I can put instance private variables with "this." in the plugin.
But if I assign an event handler of the onClick on a topic, how do I get this instance private variable? $(this) is referencing the clicked element at this moment.
Could please anyone give me an advise or a link to a tutorial how to get this done?
I only found tutorial for instance specific variables without event handlers involved.
Any help is appreciated.
Thanks in advance.
UPDATE: I cleaned out the huge code generation and kept the logical structure. This is my code:
(function ($) {
$.fn.myTreeBrowser = function (options) {
clickedElements = [];
var defaults = {
textColor: "#000",
backgroundColor: "#fff",
fontSize: "1em",
titleAttribute: "Title",
idAttribute: "Id",
parentIdAttribute: "ParentId",
levelAttribute: "Level",
treeData: {}
};
var opts = $.extend({}, $.fn.myTreeBrowser.defaults, options);
function getTreeData(id) {
if (opts.data) {
$.ajax(opts.data, { async: false, data: { Id: id } }).success(function (resultdata) {
opts.treeData = resultdata;
});
}
}
function onClick() {
var id = $(this).attr('data-id');
var parentContainer = getParentContainer($(this));
handleOnClick(parentContainer, id);
}
function handleOnClick(parentContainer, id) {
if (opts.onTopicClicked) {
opts.onTopicClicked(id);
}
clickedElements.push(id);
if (id) {
var clickedElement = $.grep(opts.treeData, function (n, i) { return n[opts.idAttribute] === id })[0];
switch (clickedElement[opts.levelAttribute]) {
case 1:
renderLevel2(parentContainer, clickedElement);
break;
case 3:
renderLevel3(parentContainer, clickedElement);
break;
default:
debug('invalid level element clicked');
}
} else {
renderTopLevel(parentContainer);
}
}
function getParentContainer(elem) {
return $(elem).parents('div.myBrowserContainer').parents()[0];
}
function onBackButtonClick() {
clickedElements.pop(); // remove actual element to get the one before
var lastClickedId = clickedElements.pop();
var parentContainer = getParentContainer($(this));
handleOnClick(parentContainer, lastClickedId);
}
function renderLevel2(parentContainer, selectedElement) {
$(parentContainer).html('');
var browsercontainer = $('<div>').addClass('myBrowserContainer').appendTo(parentContainer);
//... rendering the div ...
// for example like this with a onClick handler
var div = $('<div>').attr('data-id', element[opts.idAttribute]).addClass('fct-bs-col-md-4 pexSubtopic').on('click', onClick).appendTo(subtopicList);
// ... rendering the tree
var backButton = $('<button>').addClass('btn btn-default').text('Back').appendTo(browsercontainer);
backButton.on('click', onBackButtonClick);
}
function renderLevel3(parentContainer, selectedElement) {
$(parentContainer).html('');
var browsercontainer = $('<div>').addClass('myBrowserContainer').appendTo(parentContainer);
//... rendering the div ...
// for example like this with a onClick handler
var div = $('<div>').attr('data-id', element[opts.idAttribute]).addClass('fct-bs-col-md-4 pexSubtopic').on('click', onClick).appendTo(subtopicList);
// ... rendering the tree
var backButton = $('<button>').addClass('btn btn-default').text('Back').appendTo(browsercontainer);
backButton.on('click', onBackButtonClick);
}
function renderTopLevel(parentContainer) {
parentContainer.html('');
var browsercontainer = $('<div>').addClass('fct-page-pa fct-bs-container-fluid pexPAs myBrowserContainer').appendTo(parentContainer);
// rendering the top level display
}
getTreeData();
//top level rendering! Lower levels are rendered in event handlers.
$(this).each(function () {
renderTopLevel($(this));
});
return this;
};
// Private function for debugging.
function debug(debugText) {
if (window.console && window.console.log) {
window.console.log(debugText);
}
};
}(jQuery));
Just use one more class variable and pass this to it. Usually I call it self. So var self = this; in constructor of your plugin Class and you are good to go.
Object oriented way:
function YourPlugin(){
var self = this;
}
YourPlugin.prototype = {
constructor: YourPlugin,
clickHandler: function(){
// here the self works
}
}
Check this Fiddle
Or simple way of passing data to eventHandler:
$( "#foo" ).bind( "click", {
self: this
}, function( event ) {
alert( event.data.self);
});
You could use the jQuery proxy function:
$(yourElement).bind("click", $.proxy(this.yourFunction, this));
You can then use this in yourFunction as the this in your plugin.

Categories

Resources