Function sortAlpha is undefined - javascript

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.

Related

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

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();
}

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;
});

Set on click to `<li>` without iterating

I'm creating a dynamic <ul>, by creating a dynamic <li> tags list based on a template.
the <li> template looks something like that:
<script type="text/html" id="itemTemplate">
<li id="{{id}}">
<div class="name" title="{{name}}">{{name}}</div>
<div class="saveAs"></div>
<div class="copy"></div>
</li>
</script>
My goal is to make the saveAs and the copy div clickable and execute a function with the id as a parameter.
Iv'e managed to do that by this function:
function myView() {
self.itemTemplate = null;
self.myArrayOfObjects = null;
self.initItemsUl = () => {
self.itemsUl = self.mainContainer.find('.itemsUl');
self.myArrayOfObjects.forEach(self.initItemLi);
};
self.initItemLi = (item) => {
var viewObj = {
id: item.Id,
name: item.Name
};
var itemLi = $(Mustache.render(self.itemTemplate, viewObj));
self.mainContainer.append(itemLi[0]);
self.setupItemOnClick(itemLi, item.Id);
};
self.setupItemOnClick = (itemLi, id) => {
itemLi.find('.saveAs').on('click', null, () => {
//do_something(id)
});
itemLi.find('.copy').on('click', null, () => {
//do_something(id)
});
};
return {
init: (myArrayOfObjects) => {
self.myArrayOfObjects = myArrayOfObjects;
self.itemTemplate = $('#itemTemplate').html();
Mustache.parse(self.itemTemplate);
self.initItemsUl();
}
};
}
Pay attention that the function setupItemOnClick is being called every time i'm rendering the li template, my question is how to make this function to be called only once?
Use event delegation on the ul, rather than handlers on the individual .saveAs and .copy elements:
$("selector-for-your-ul")
.on("click", ".saveAs", e => {
// Handle click here, `e.currentTarget` is the `.saveAs` that was clicked
})
.on("click", ".copy", e => {
// Handle click here, `e.currentTarget` is the `.copy` that was clicked
});
Re your comment:
...how do i get the id of the parent li out of the e object?
By using $(e.currentTarget).closest("li").attr("id").

Mixin functions only work in render()

For some reason, it appears that mixin functions in my code only work properly in render() function. It could be that I'm not calling them in the right manner outside of the render(), but shouldn't it be exactly the same way?
This way everything works fine (but I can't stick with this since I have to add some extra stuff to click handling, at the same time not altering the mixin):
var Row = React.createClass({
mixins: [someMixin]
},
render: function () {
var clickHandler = null;
var btn = null;
if (firstCase) {
clickHandler = this.order(this.props.name, this.props.else);
btn = (<a href="" onClick={clickHandler}>Order</a>);
} else if (secondCase) {
clickHandler = this.noOrder(this.props.name, this.props.else);
btn = (<a href="" onClick={clickHandler}>No order</a>);
}
return (
<div>
{btn}
</div>
);
}
});
But when I do the obvious and include the mixin functions in another function to handle the click - like this - everything fails and even 'test' is not printed in the console:
var Row = React.createClass({
mixins: [someMixin]
},
handleOrderClick(type) {
console.log('test');
if (type == 'order') {
this.order(this.props.name, this.props.else);
} else if (type == 'no-order') {
this.noOrder(this.props.name, this.props.else);
}
},
render: function () {
var clickHandler = null;
var btn = null;
if (firstCase) {
clickHandler = this.handleOrderClick('order');
btn = (<a href="" onClick={clickHandler}>Order</a>);
} else if (secondCase) {
clickHandler = this.handleOrderClick('no-order');
btn = (<a href="" onClick={clickHandler}>No order</a>);
}
return (
<div>
{btn}
</div>
);
}
});
EDIT:
order and noOrder functions look like this:
order: function (name, else) {
return function (event) {
event.preventDefault();
var term = name + '&&ยค%' + else;
Order.order(name, else, period, function (data) {
if (term === (global || window).MAIN_NAME + '.' + (global || window).MAIN) {
$(window).trigger('Name:update');
}
}.bind(this));
}.bind(this);
},
noOrder: function (name, else) {
return function (event) {
event.preventDefault();
if (!this.state.transferModalOpen) {
this.toggleTransferModal();
}
}.bind(this);
}
In order to use this.setState in handleOrderClick you'll have to use the bind method in your render method. Therefore handleOrderClick will become:
handleOrderClick(type, event) {
this.setState({foo: 'bar'});
if (type == 'order') {
this.order(this.props.name, this.props.else)(event);
} else if (type == 'no-order') {
this.noOrder(this.props.name, this.props.else)(event);
}
},
and your render method becomes:
render: function () {
var clickHandler = null;
var btn = null;
if (firstCase) {
clickHandler = this.handleOrderClick.bind(this, 'order');
btn = (<a href="" onClick={clickHandler}>Order</a>);
} else if (secondCase) {
clickHandler = this.handleOrderClick(this, 'no-order');
btn = (<a href="" onClick={clickHandler}>No order</a>);
}
return (
<div>
{btn}
</div>
);
}
You'll notice that the functions that are returned by this.order and this.noOrder are no longer returned by handleOrderClick, but are instead executed immediately. This should provide the effect you desire.
I've put the code in your example into a jsfiddle and it now seems to be working correctly. I've had to change the prop 'else' to 'alt' because 'else' is a reserved word. I've also just applied the mixin to the class directly for simplicity. I have simpilfied the order and noOrder functions as I don't have access to the Order object and we are only interested in them firing at the correct time. I've also added a second button that you can click to flip the cases so the other button is rendered, causing the component to render again. I've added a label that will display which function had been called when the button was last pressed.
for reference, you can find more information about the bind method here.
Hope this helps ^_^

Class methods as event handlers in JavaScript?

Is there a best-practice or common way in JavaScript to have class members as event handlers?
Consider the following simple example:
<head>
<script language="javascript" type="text/javascript">
ClickCounter = function(buttonId) {
this._clickCount = 0;
document.getElementById(buttonId).onclick = this.buttonClicked;
}
ClickCounter.prototype = {
buttonClicked: function() {
this._clickCount++;
alert('the button was clicked ' + this._clickCount + ' times');
}
}
</script>
</head>
<body>
<input type="button" id="btn1" value="Click me" />
<script language="javascript" type="text/javascript">
var btn1counter = new ClickCounter('btn1');
</script>
</body>
The event handler buttonClicked gets called, but the _clickCount member is inaccessible, or this points to some other object.
Any good tips/articles/resources about this kind of problems?
ClickCounter = function(buttonId) {
this._clickCount = 0;
var that = this;
document.getElementById(buttonId).onclick = function(){ that.buttonClicked() };
}
ClickCounter.prototype = {
buttonClicked: function() {
this._clickCount++;
alert('the button was clicked ' + this._clickCount + ' times');
}
}
EDIT almost 10 years later, with ES6, arrow functions and class properties
class ClickCounter {
count = 0;
constructor( buttonId ){
document.getElementById(buttonId)
.addEventListener( "click", this.buttonClicked );
}
buttonClicked = e => {
this.count += 1;
console.log(`clicked ${this.count} times`);
}
}
https://codepen.io/anon/pen/zaYvqq
I don't know why Function.prototype.bind wasn't mentioned here yet. So I'll just leave this here ;)
ClickCounter = function(buttonId) {
this._clickCount = 0;
document.getElementById(buttonId).onclick = this.buttonClicked.bind(this);
}
ClickCounter.prototype = {
buttonClicked: function() {
this._clickCount++;
alert('the button was clicked ' + this._clickCount + ' times');
}
}
A function attached directly to the onclick property will have the execution context's this property pointing at the element.
When you need to an element event to run against a specific instance of an object (a la a delegate in .NET) then you'll need a closure:-
function MyClass() {this.count = 0;}
MyClass.prototype.onclickHandler = function(target)
{
// use target when you need values from the object that had the handler attached
this.count++;
}
MyClass.prototype.attachOnclick = function(elem)
{
var self = this;
elem.onclick = function() {self.onclickHandler(this); }
elem = null; //prevents memleak
}
var o = new MyClass();
o.attachOnclick(document.getElementById('divThing'))
You can use fat-arrow syntax, which binds to the lexical scope of the function
function doIt() {
this.f = () => {
console.log("f called ok");
this.g();
}
this.g = () => {
console.log("g called ok");
}
}
After that you can try
var n = new doIt();
setTimeout(n.f,1000);
You can try it on babel or if your browser supports ES6 on jsFiddle.
Unfortunately the ES6 Class -syntax does not seem to allow creating function lexically binded to this. I personally think it might as well do that. EDIT: There seems to be experimental ES7 feature to allow it.
I like to use unnamed functions, just implemented a navigation Class which handles this correctly:
this.navToggle.addEventListener('click', () => this.toggleNav() );
then this.toggleNav() can be just a function in the Class.
I know I used to call a named function but it can be any code you put in between like this :
this.navToggle.addEventListener('click', () => { [any code] } );
Because of the arrow you pass the this instance and can use it there.
Pawel had a little different convention but I think its better to use functions because the naming conventions for Classes and Methods in it is the way to go :-)

Categories

Resources