Im working on a heavily javascript based site with multiple language settings. The languages are stored in an external file, f e app.en.js and app.de.js. Both of these define the lang Object:
lang = {
introText0 : 'Lorem',
introText1 : 'Ipsum',
introText2 : 'Dolor'
}
Now i have a function responsible for changing the language:
function changeLanguage(language) {
delete lang;
if(language === 1) { $.getScript('app.en.js'); }
if(language === 2) { $.getScript('app.de.js'); }
}
So far so good. This works in theory. All over the app are references to the lang Object, replacing every text. Every text that gets referenced is being updated by this change, however, there are arrays that also refer to these, and they don't update properly.
var upgradeItems = new Array();
upgradeItems[0] = new Array(lang.upgradeItem0, 13, 5, true);
I take it that the garbage Collection of JavaScript only actually deletes unreferred properties of an Object, and since these got referred before the changeLanguage() happened, they stay in the app. How do i work around this and 'refresh' these Arrays?
Related
I'm a new dev coming from a Ruby background. Recently I've been working very hard in JS and I'm having some issues with class inheritance in the new ES 6. I feel like it may be an issue with my understanding of JS or conflating it to much to Ruby. I've been trying to translate a Ruby Project into JS to practice, but I'm now failing a feature test.
Failing Feature test when trying to initialize two instances of a class
const STATS = { str:1, dex:1, int:1 }
class Example {
constructor(race, clas) {
this.race = race,
this.clas = clas,
this.stats = this.add(STATS)
}
add(stats) {
if(this.race != 'empty'){
stats.str += this.race.str
stats.dex += this.race.dex
stats.int += this.race.int
}
if(this.clas != 'empty') {
stats.str += this.clas.str
stats.dex += this.clas.dex
stats.int += this.clas.int
}
return stats
}
}
var a = new Example({str: 1, dex:0, int:0}, 'empty');
var b = new Example('empty', {str: 0, dex:0, int:1});
console.log('Should be str:2 dex:1 int:1');
console.log(a.stats);
console.log('Should be str:1 dex:1 int:2');
console.log(b.stats);
My class has functions that change the state when constructed, but the issue is any time a new Class is called it retains the changes from the previous variable. It is only an issue on my feature test as it is the only time that class is called twice.
This is the link to my feature test https://github.com/RyanWolfen7/jsRPG/blob/master/cypress/integration/featureTest/characterFeature_test.js
And this is the class thats failing the tests
https://github.com/RyanWolfen7/jsRPG/blob/master/models/characters/character.js
Honestly I'm probably going to scrap my project and start fresh anyways, but I would like to understand what my issue is. I was taking an OOD approach to JS and translating my ruby project https://github.com/RyanWolfen7/ruby_rpg to JS. I'm not sure if its because I wrote the test wrong or a deep misunderstanding of how es-6 works.
Things I have tried:
Creating a new object
Assigning a newly created object to new class
It's not an inheritance issue. In fact, it has nothing to do with OO at all. What you are seeing is the result of the fact that most things in javascript are references (pointers) but you are writing code as if STATS is a value.
In your function add you do this:
add(stats) {
if(this.race != 'empty'){
// modifying passed in object, not creating new object
stats.str += this.race.str
stats.dex += this.race.dex
stats.int += this.race.int
}
if(this.clas != 'empty') {
// modifying passed in object, not creating new object
stats.str += this.clas.str
stats.dex += this.clas.dex
stats.int += this.clas.int
}
return stats
}
So no matter how many times you call add() and from whichever instance of Example you call it from you are only accessing and overwriting the single shared STATS object.
To create new copies of STATS on each function call you need to copy it to a new object. The fastest old-school way to do this is to serialise the object to a string then convert the string back to an object:
add (input) {
var stats = JSON.parse(JSON.stringify(input));
// ...
}
This feels ugly but multiple benchmarks really did show it to be the fastest method.
Modern javascript can do this using Object.assign:
add (input) {
var stats = Object.assign({},input);
// ...
}
However, I don't know if it is faster. You will have to benchmark it yourself. You can google the phrase "js clone object" for more info.
Context
I have been tasked with fixing a big bug on the menu-edit page, which was caused by a stale element issue, caused by the HTML elements for it being rendered server-side. In my three-day fight against this bug, I got some inspiration from Angular and decided to try to make a menu state that will power everything on the page (adding/removing categories/items, and later, pagination of the modals for the adding)
Some Code
I came up with this IIFE (to be the "controller" of the MVC. Selector modals hit the add methods of this, and delete buttons hit the remove methods of this. Also, this gets passed to template-render function, which is literally the first thing hit when a modal gets popped):
/* all the categories, items, and modifiers that power this page */
var menuState = (function() {
let _categories = {
attached: [],
available: []
}, _items = {
attached: [],
available: []
}, _modifiers = {
attached: [],
available: []
}
function getExposedMethodsFor(obj) {
return {
all : function() { return obj.attached.concat(obj.available) },
attached : function() { return obj.attached },
available : function() { return obj.available }
// ... other methods that work on obj.attached,obj.available
}
}
let categoryExposedMethods = getExposedMethodsFor(_categories)
// other exposer objects
return {
getAllCategories : categoryExposedMethods.all,
getAttachedCategories : categoryExposedMethods.attached,
getAvailableCategories : categoryExposedMethods.available
// the rest of the exposed methods irrelevant to this question at hand
}
})()
OK, so what's the problem?
The problem is that this is false sense of security, it seems. When I try to XSS-test this structure alone, it fails.
I test it with three entities in _categories, all of which are attached, causing
menuState.getAllCategories().length
to return 3 and
menuState.getAvailableCategories().length
to return 0. Good news is that when I tried
menuState.getAllCategories().push('a')
menuState.getAllCategories().length
I still get three.
However, when I go
menuState.getAvailableCategories().push('b')
menuState.getAvailableCategories().length
I get 1, instead of 0 !!
Is there truly a way to lock down the other getters here?! If not, what are my alternatives?
I fixed it with Object.freeze, which I already used for refactoring the "enums" the dev before me wrote when he was working on this project. What it does is fully protect a state from any type of changes, including:
adding properties
deleting properties
modifying properties
re-assigning the object/array being "frozen"
How I use it
In the helper method, I did the following :
attached : function() { return Object.freeze(obj.attached) },
available : function() { return Object.freeze(obj.available) },
This prevents the arrays being changed from those methods, thus shutting down this type of XSS. Also, menuState was declared with const.
This is my first pass at this task i have. I need to update my UI based on the field. The field can be of different types. Here I am just checking for a memo or boolean type.
// UI Field Rule set.
var UIFieldRules = {
isMemo: function() {
return this.DataType === DataTypeKVP("Memo");
},
isBoolean: function() {
return this.DataType === DataTypeKVP("Boolean");
},
MapToList: function() {
if (UIFieldRules.isMemo.call(this) || UIFieldRules.isBoolean.call(this)) {
console.log("memo or bool");
console.log(UIFieldRules.isMemo.call(this));
console.log(this);
MAPTOLIST_SELECTOR.prop('disabled', true);
return;
} else {
MAPTOLIST_SELECTOR.prop('disabled', false);
console.log("UI field rules found memo");
}
}
};
I then call this object upon loading all the fields.
UIFieldRules.MapToList.call(field);
This works fine and satisfied the task, but now i need to apply more rules to the fields. (stop me if you heard this one before)
How can I get this set where i can just add a rule to a collection and have them all applied dynamically in javascript?
Update provide example:
function MapToList(field){
isBoolean:function(){}
isMemo : function(){}
execute : function(){
if (UIFieldRules.isMemo.call(this) || UIFieldRules.isBoolean.call(this)) {
console.log("memo or bool");
console.log(UIFieldRules.isMemo.call(this));
console.log(this);
MAPTOLIST_SELECTOR.prop('disabled', true);
return;
} else {
MAPTOLIST_SELECTOR.prop('disabled', false);
console.log("UI field rules found memo");
}
}
}
Then if i want to create more rules (which I do) should I create another object like the one above? Is there a best practice way of doing this in JS?
var rules = [];
rules.push(new MapToList(field));
rules.push(new RegExEnabled(field));
$.each(rules,function(item){
item.execute();
});
Your example approach is exactly fine. Create multiple objects that all implement the same interface, put them in a list, and then call a common method on each of them:
var rules = [MapToList, RegExEnabled];
rules.forEach(function(item){
item.execute(field);
});
However, you might want to notice that you typically you don't need a constructor + new if your object is not stateful or does not have any parameterisation, a simple object literal is enough.
And similarly, if your shared interface boils down to a single execute method, what you actually want is not a list of objects but just a list of functions you can call. It's not Java :-)
Is there a way in javascript to create an object from a string?
Here is an example :
configuation object:
var formatter = {
telephone : {
length : 10,
formatClass : "telephoneFormatter"
},
email : {
length : 255,
formatClass : "emailFormatter"
}
}
In the fields creation I could use the following method :
function createFormatter(element){
if(formatter[element].formatClass == "telephoneFormatter"){
var formatObj = new telephoneFormatter()
}
if(formatter[element].formatClass == "emailFormatter"){
var formatObj = new emailFormatter()
}
return formatObj;
}
But I would like to create a the object dynamically, something like
function createFormatter(element){
return new formatter[element].formatClass();
}
The constructors are not available as properties of the window object, as presented in the solution to "Dynamic Instantiation In JavaScript". The class files are loaded with the page but I cannot find the object developer's tool in chrome. Thus I do not have a handle currently on the classes.
Why do I need to do that ? The application loads form dynamically, and the field are created from an elaborate JSON. While the form is beeing created, the validation is added depending on the structure of the JSON. We never know what validation must be added to a certain field. To complexify the whole thing the validation is different on the locale.
Can that be done?
you can create a custom factory function which checks the configurationObjects formaterClass and than initializes a class of that type. than pass the configurations object as constructor parameter to initialize the object.
register the available classes so the factory function is not a multi condition block.
var formatterClasses = {
'telephoneFormatter': telephoneFormatter,
'emailFormatter': emailFormatter
}
function formatterFactory(configurationObject)
{
return new formatterClasses[configurationObject.formatClass](configurationObject);
}
function MySingletonClass(arg) {
this.arr = [];
if ( arguments.callee._singletonInstance )
return arguments.callee._singletonInstance;
arguments.callee._singletonInstance = this;
this.Foo = function() {
this.arr.push(arg);
// ...
}
}
var a = new MySingletonClass()
var b = MySingletonClass()
Print( a === b ); // prints: true
My requirement is i am pushing objects to an array on each load of window, but when i open the next window the state of the array is not visible.
var arr = [];
arr.push("something");
// It gets pushed.
When i open the new window, the array's length becomes zero again.
There is no way to do this with JavaScript alone. JavaScript is just the language. It doesn't have any direct link to the app, the page or even the browser. JavaScript can be used (and is used) in many other situations, such as in server-side applications and as a plugin language for desktop apps.
Of course, when JavaScript is used in the browser, you do need a way to "communicate", as it were, with the content on page. For this you can use the Document Object Model (DOM) API, which is implemented by every browser that supports JavaScript. To communicate with the browser itself you can use window and other global object. These are sometimes referred to as the Browser Object Model (although it's not an official API).
Now that we know that; is there an API that allows us to maintain state between pages? Yes, there is. In fact, there are several:
HTML5's localStorage
Cookies
Take this example, using localStorage:
// On page 1:
localStorage.setItem("message", "Hello World!");
// On page 2:
var message = localStorage.getItem("message");
if (message !== null) {
alert(message);
}
Easy, right? Unfortunately, localStorage only accepts key/value pairs. To save an array, you'll need to convert it into a string first. You could do this, for example, using JSON:
// On both pages:
var arr = localStorage.getItem("arr");
if (arr === null) {
arr = [];
} else {
arr = JSON.parse(arr);
}
function saveArr() {
localStorage.setItem("arr", JSON.stringify(arr));
}
// On page 1:
console.log(arr); // []
arr.push("Hello");
arr.push("world!");
saveArr();
// On page 2:
console.log(arr); // ["Hello", "world!"]
Keep in mind, though, that localStorage and JSON are both fairly new, so only modern browsers support them. Have a look at emulating localStorage using cookies and at JSON2.js.
For data to persist across an application, there must be a database. Javascript cannot accomplish this because it is client side only and mostly intended as a way to render user interfaces.