Categories
Web Development

Javascript event listeners be careful where you bind this

In Javascript User interface programming you often need to work with EventListeners. In modern Javascript you may also be doing this in a class which is handy. One of the  things that sucks about Javascript event listeners is they eat memory and slow your app down if you are not REALLY careful.

sweet meme
Slow down your app the easy way!

They do this because each time the action you use to add the event listener code fires more code gets added to memory, more things need to be kept up with etc.

There are some best practices that help with this issue such as always remembering to remove the event listeners, never use anonymous functions, arrow functions etc. Here on MDN there is a lot of good information I won’t waste time repeating in this article.

What I want to cover in this article is properly binding this within a class when using Event Listeners so that the function signatures match and therefore the listeners can properly be removed. Don’t think reloading the page solves your problem either… it doesn’t. In fact it just makes the browser eat more and more memory.

The reason we need to bind this is the code in our Javascript classes that handle the events ( event handlers ) usually need to access other methods and properties within the class when called by the event listener code.

Magical Bugs

Worse than the memory issue is the magical, insanely, nearly impossible bugs that having multiple listeners calling the same function can create.

If for example the function changes a variable value so that some UI action changes etc. One example I just ran into was a method in a class that monitored a keypress then moved the cursor. This was for an auto completer with a drop list of suggestions. The idea was to have Javascript highlight the selected option and accept it when the user hits enter. Below is the code example.

keyPressHandler(event) {

        let target = event.target.id;
        this.keyPressed = event.keyCode;
        this.optionElementsList = document.getElementById(target + "-ez-hashtags-list");

        if (this.optionElementsList) {
            this.optionElementsList = this.optionElementsList.getElementsByTagName("div");
        }

        switch (event.keyCode) {
            /*If the arrow DOWN key is pressed,
           increase the currentChoice variable:*/
            case 40:
                console.log('inside keyPressHandler handling keydown event');
                this.currentChoice++;
                this.makeActive();
                break;
            /*If the arrow UP key is pressed,
       decrease the currentFocus variable:*/
            case 38 :
                console.log('inside keyPressHandler handling keydown event');
                this.currentChoice--;
                this.makeActive();
                break;
            /*If the ENTER key is pressed, prevent the form from being submitted,*/
            case 13 :
                event.preventDefault();

                if (this.currentChoice > -1) {
                    /*and simulate a click on the "active" item:*/
                    if (this.optionElementsList) {
                        this.optionElementsList[this.currentChoice].click();
                    }
                    //empty the keysTyped variable
                    this.keysTyped = '';
                }
                break;
            case 8 :
            case 9 :
                //9 is tab, close any list if they hit tab
                //8 is backspace close the current list if they hit backspace
                this.closeOpenLists();
                break;
        }
    }

This is similar to what you need to do when creating a custom WYSIWYG html editor. You need to monitor every damn key pressed and perform actions based on that. The real fun begins when you have two listeners say one for keyup and one for keydown.

The code above if the user hits the up or down arrow decreases or increases the variable used to track the users expected action. It then calls another method to make the correct option highlighted for the user as selected. Below is the code.

makeActive() {

        if (!this.optionElementsList) {
            return false;
        }
        /*start by removing the "active" class on all items:*/
        this.makeInactive();

        if (this.currentChoice >= this.optionElementsList.length) {
            this.currentChoice = 0;
        }
        if (this.currentChoice < 0) {
            this.currentChoice = (this.optionElementsList.length - 1);
        }
        /*add class "autocomplete-active":*/
        this.optionElementsList[this.currentChoice].classList.add("ez-hashtags-active");
    }

This is where the currentChoice variable comes in play. The up and down arrows add the class which changes the background color to make it look highlighted. Then when the user hits enter, the selected value is entered into the input, but in this case a div with contenteditable=true This UI stuff is a real pain in the ass.

The BAD CODE

Now lets look at the wrong way to bind this, even though it looks like this should totally work properly.

 createChoiceList(elementId, inputValue) {

        if (Utils.isEmpty(inputValue)) {
            return false;
        }
        this.currentChoice = -1;

        if (!Utils.isEmpty(this.listOptions)) {

            if (Utils.notEmpty(this.optionContainer)) {
                this.closeOpenLists();
            }

            /*create a DIV element that will contain the items (values):*/
            this.optionContainer = document.createElement("DIV");
            this.optionContainer.setAttribute("id", elementId + "-ez-hashtags-list");
            this.optionContainer.setAttribute("class", "ez-hashtags-items");
            /*append the DIV element as a child of the hashtag div container:*/
            this.hashtagsDivElement.parentNode.appendChild(this.optionContainer);

            /*for each item in the array...*/
            for (let i = 0; i < this.listOptions.length; i++) {

                /*check if the item starts with the same letters as the text field value:*/
                let firstLetter = this.listOptions[i].substr(0, inputValue.length).toUpperCase();

                if (firstLetter === inputValue.toUpperCase()) {

                    /*create a DIV element for each matching element:*/
                    let b = document.createElement("DIV");
                    /*make the matching letters bold:*/
                    b.innerHTML = "#" + this.listOptions[i].substr(0, elementId.length) + "";
                    b.innerHTML += this.listOptions[i].substr(elementId.length);
                    /*insert an input field that will hold the current array item's value:*/
                    b.innerHTML += "";
                    /*execute a function when someone clicks on the item value (DIV element):*/
                    this.optionContainer.appendChild(b);
                }
            }
            this.hashtagsDivElement.addEventListener('keydown', this.keyPressHandler.bind(this));
            this.optionContainer.addEventListener('click', this.choiceSelectionHandler.bind(this));
        }
    }

The offending code is the last two lines that add the even listeners. The intention here was to make the signature match the remove event listener code. Like  this below.

removeKeypressHandlers() {
        try {

            if (this.hashtagsDivElement) {
                this.hashtagsDivElement.removeEventListener('keydown',this.keyPressHandler.bind(this));
            }

            if (this.optionContainer) {
                this.optionContainer.removeEventListener('click', this.choiceSelectionHandler.bind(this));
            }

        } catch (e) {
            console.log("OOPS we caught an error " + e);
        }
    }

It looks good, like they match like it should function, right?

makes sense to me meme
Looks good to me…

WRONG

Even though those functions look like they match, bind(this) creates a new function every time it is called. So now we have LOTS OF EVENT LISTENERS. And that causes magical bugs like the video below.

Notice how when I first type the selection works. Then when I backspace and start again it just jumps to the last one. Then I backspace and start again and it works. Then it doesn’t I was like WTFF I am losing my mind. Watch towards the end for the magic to start really good.

God forbid you ever meet an articulate ass.

articulate ass
Meet the articulate ass

The right way

So how should we defeat this Javascript demon and bind this properly for our event handlers??? Well the answer is quite simple actually. You create another new variable in the constructor and bind this to the handler there. This way bind creates only 1 function, 1 time and it can easily be removed. As a bonus if you add the function 100 times in 100 calls, the browser will only actually add it 1 time.

So lets see the code.

        constructor(inputElementId, endpointUrl, fetchOnce = true, keysThreshold = 2) {

        //set the routes for the Routing object
        Routing.setRoutingData(routes);
        this.inputElementId = inputElementId;
        this.currentChoice = -1;
        this.endpointUrl = endpointUrl;
        this.fetchOnce = fetchOnce;
        this.inputElement = document.getElementById(inputElementId);
        this.hashtagsDivElement = null;
        this.hashtagValues = '';
        this.inputValues = '';
        this.optionContainer = null;
        this.optionElementsList = null;
        this.listOptions = '';
        this.keyPressed = '';
        this.keysTyped = '';
        this.boundChoiceSelectionHandler = this.choiceSelectionHandler.bind(this);
        this.boundKeypressHandler = this.keyPressHandler.bind(this);
        this.boundTypingHandler = this.handleTyping.bind(this);
        //add the hashtags div and hide the actual input element
        this.hideInputElement();
        this.addHashtagsDiv();
    }

As you can see in the code above,this goes in the class constructor. Anywhere else and you could be creating the same listeners over and over.

Then to actually add the listeners when you want them to be added you do this little trick below.

this.inputElement.addEventListener('keydown', this.boundKeypressHandler);
            this.optionContainer.addEventListener('click', this.boundSelectionHandler);
Notice now the binding is done in the constructor and the variable that holds the bound function is added in place of the previous binding in the addEventListener() calls. This way these eventListeners are only ever created once, the other times are ignored. Javascript bind actually creates and returns a new function with the scope needed to use this keyword.

To remove the event listeners later to cleanup memory, you do this.
removeKeypressHandlers() {
        try {

            if (this.inputElement) {
                console.log('Removing the input element keydown handler');
                this.inputElement.removeEventListener('keydown', this.boundKeypressHandler);
            }

            if (this.optionContainer) {
                console.log('removing the option container click handler');
                this.optionContainer.removeEventListener('click', this.boundSelectionHandler);
            }

        } catch (e) {
            console.log("OOPS we caught an error " + e);
        }

    }

Notice now the signatures of the calls match exactly this.boundxxx

I had learned this year information many years ago and forgot it until I had this hard to catch bug above. I then finally remembered I was was doing stupid things and how to do this properly.

These kind of things will make you hate Javascript UI programming or any DOM action programming. I hope I never forget this lesson again.

Categories
Uncategorized

How to update your FosJsRoutingBundle routes file

When you add a new route to one of your Symfony API routes aka your controllers, you will need to recreate the JSON routing file that FosJsRoutingBundle uses.

To do that you use this simple command.

php bin/console fos:js-routing:dump --format=json --target=public/js/fos_js_routes.json

This command will rewrite your existing routes JSON file located in the public folder of your project.

Then to make sure your routes are working you display them with this command.

php bin/console fos:js-routing:debug

This will output your currently available routes created with the line above.
To learn more about the command read this short article. How to view Symfony 5+ FosJsRoutingBundle routes

To learn how to get the Javascript routes in your javascript code in the first place with FosJsRoutingBundle read this book I wrote on the subject. LOL How to get URL Routes in your Javascript in Symfony 5+

Categories
Resources Web Development

How to view Symfony 5+ FosJsRoutingBundle routes

I can never remember this command. However, now that I am creating more AJAX and exposing endpoints I need this command more often.

This command is found in the docs too. But I can never remember where, so I will write about it here.
The command to view how FosJsRoutingBundle views your routes.

php bin/console fos:js-routing:debug

This will list something like this for you.

 Name                  | Method | Scheme | Host | Path                   |
+-----------------------+--------+--------+------+------------------------+
| menu                  | GET    | ANY    | ANY  | /menu                  |
| alias_exists          | ANY    | ANY    | ANY  | /alias_exists          |
| email_exists          | ANY    | ANY    | ANY  | /email_exists          |
| get_image_collections | GET    | ANY    | ANY  | /get_image_collections |
| save_user_image       | POST   | ANY    | ANY  | /save_user_image       |
| get_user_image_data   | GET    | ANY    | ANY  | /get_user_image_data   |
+-----------------------+--------+--------+------+------------------------+

Isn’t that pretty.This is not dynamically updated. You need to re-output your routes to the file FosJSRoutingBundle expects them to be in, so that when it creates a route it matches. I need to write an article about how to do only that. For now scroll to the bottom of the following article. How to get URL Routes in your Javascript in Symfony 5+

But if you don’t update your routes they won’t match the above output and your routes will fail when you use FosJS. Mine seemed to just redirect to my current page. Your setup/configs may differ.

Categories
Resources Web Development

How to view Symfony routes

I often need to view my current routes and how the Symfony kernel views them. This usually happens when I am adding new routes, I end up getting conflicts and have to resolve them by viewing what exists etc.

The command to view your routes is really simple. Open your console and navigate to your projects main directory and type the following command to see a list of all of your current Controllers routes.

php bin/console debug:router

That will output a list of all of your routes. Something like this

user_settings                 GET        ANY      ANY    /user-settings                       
  profile_unavailable           GET        ANY      ANY    /profile-unavailable{page_data}      
  save_user_settings            POST       ANY      ANY    /save-user-settings                  
  _preview_error                ANY        ANY      ANY    /_error/{code}.{_format}             
  _wdt                          ANY        ANY      ANY    /_wdt/{token}                        
  _profiler_home                ANY        ANY      ANY    /_profiler/                          
  _profiler_search              ANY        ANY      ANY    /_profiler/search                    
  _profiler_search_bar          ANY        ANY      ANY    /_profiler/search_bar                
  _profiler_phpinfo             ANY        ANY      ANY    /_profiler/phpinfo                   
  _profiler_search_results      ANY        ANY      ANY    /_profiler/{token}/search/results    
  _profiler_open_file           ANY        ANY      ANY    /_profiler/open                      
  _profiler                     ANY        ANY      ANY    /_profiler/{token}                   
  _profiler_router              ANY        ANY      ANY    /_profiler/{token}/router            
  _profiler_exception           ANY        ANY      ANY    /_profiler/{token}/exception         
  _profiler_exception_css       ANY        ANY      ANY    /_profiler/{token}/exception.css     
  show_dash                     ANY        ANY      ANY    /admin                               
  tos                           GET        ANY      ANY    /app/pages/tos                       
  privacy                       GET        ANY      ANY    /app/pages/privacy                   
  block_user                    POST       ANY      ANY    /block-user                          
  unblock_user                  POST       ANY      ANY    /unblock-user                        
  blocked_users                 GET        ANY      ANY    /blocked-users                       

As you can see there are 5 columns. The first column is the routes name, tos for example. Here is how tos is declared in the Controller

* @Route("/app/pages/tos", name="tos", methods={"GET"})

The second column is what type of HTTP request is allowed POST, GET, HEAD etc. The next two columns are the Scheme and Host, dig in the docs under routing, I believe, if you want more info on those two. The final fifth column is the Path, this is what your URL should look like.

Categories
Software Development Web Development

Symfony how to get query string values sent by AJAX calls

When using a Symfony API endpoint for getting values such as maybe something like an auto-complete feature you will need to be able to send some text(what the user enters) to the backend. Then have the backend return a response based on that.

For example with an auto complete feature, you might want to send 3 characters to an API endpoint and have it only return a list of words that start with those three characters, instead of returning every last word in the database. This will use less of your servers resources and the users while speeding your app up.

The way I suggest to do this is to first use FosJsRouting bundle in your Javascript to create the URL’s for you. This makes it much easier, faster ( Once you get it installed etc ) and consistent. Here is a long article I wrote about installing and using FosJsRouting bundle How to get URL Routes in your Javascript in Symfony 5+.

This is a short example and doesn’t include checking if the user is logged in ( has rights) if it is an AJAX request, check headers etc. it is just for this example.



/**
     * returns a list of the users image collection names
     * @Route("/get_image_collections", name="get_image_collections", options={"expose"=true},  methods={"GET"})
     */
    public function get_image_collections(Request $request): JsonResponse
    {
        $query = $request->getQueryString();
      $text2 = $request->query->get('text');
      $text =  $request->get("text");
        $values = array(
            'first',
            'second',
            'third',
            'fan',
            'free',
            'narcotics',
            'arse',
            'tardigrade',
            'tinnitus',
            'monkey',
            'mall',
            'doppleganger',
            'ballocks',
            'zoo',
            $query,
            $text,
            $text2
        );
        return new JsonResponse($values);
    }

This is very simple, it doesn’t even hit a database, it is just for this article and testing. All this does right now is return the list and add some text and query so I can see what was sent. In production you might even want to use caching so that your database only gets hit if it has to.

As you can see there appears to be two ways to get the value of the query. Both worked for me, but I am thinking that going through the query method is the proper way like follows.

$request->query->get('text');

The query being used for this endpoint looks like this

"http://sogi-test/get_image_collections?text=fa"

In the future I’ll update the code to include security checks, to return a matching list compiled from a database query and caching.

But notice the (Request $request) that is Dependency Injected ( see symfony dependency injection )

I should also add, below is how I am building the URL with FosJsBundle

let url = Routing.generate('get_image_collections', {text: text});

That is where the “text” named query key is coming from. You can name the key anything you want. If you have more than one named value then use a comma and add it after the first, like in the FosJsRoutingBundle docs under how to use above link.
For example say you want to include something like a page number for paging through results or limiting them etc. You could create the
url route like this

let url = Routing.generate('get_image_collections', {text: text, limit: limit});

This Javascript JSON looks confusing, the first part is the name(key) the second is a(value) variable containing the value
Then to get the values in your controller route you do this.


$text = $request->query->get('text');
$limit = $request->query->get('limit');

You can send as many values as you need to this way. Forms work a little differently. I’ll write an article about those soon too.

And that is how you get the values you send.
baby how it is done meme
That is how it is done

Interesting Error story

I figured I would mention this debugging story I had while sending an ajax request with an improperly formed URL ( aka not matching the route)

I kept getting errors when trying to use Promise.json() with the value returned from my AJAX call. What happened is the server was returning HTML instead of JSON which results in an error when Promise.json() is called.

A while of that and thinking it was my JS code but not really sure of WTF was going on. I finally started outputting my URL to see what was being created and low and behold that was it. I was even watching the Request/Response in the browser. But, it was not obvious until I really started inspecting the created URL vs the route output that I figured out my route and url didn’t match and that was the reason for the redirect.

How to view Symfony routes

 

My route didn’t match because at some point I changed how I wanted the route to look and I had not rerun the FosJsRouting bundle as mentioned in that article to renew the routes in it’s json file.  So it created a URL that didn’t match, the route was created like /route/{text} but the api route expected /route&text=….

I didn’t get an error. Nope and nothing was recorded that I could find in the error logs. Symfony simply redirected ( 301) sent back to my ajax which then somehow did another request to the page I was using the javascript in,  returning the pages HTML.

I was like

Uhm, wait… what?

I have no idea why Symfony redirects when the route doesn’t match.  I don’t even know what setting to adjust where to change this, I know I did something somewhere at sometime… I’ll have to dig into what I did.

Why this happend is because I didn’t re-update the FosJsRouting json file and then restart webpack so it pulls in the new file. But I know there is some sort of setting somewhere for Symfony for the redirect action I am seeing and I know I set it somewhere somehow.

Can I have hamburger now???

Categories
Web Development

How to quickly create a Symfony 5+ controller

A Symfony 5 controller is just a class. You could just use your IDE to create a new class  for you, but you would need  to add some boiler plate code like the namespace and then extend AbstractController and add some use statements etc.

Symfony 5+ has a better way though. With just one command you can have a new controller created with the basic boiler plate already created. How do you ask? With a simple Symfony maker command.

php bin/console make:controller NewControllerName

IT is that easy. Now you can go to the new Controller and start adding methods. Here is a deeper explanation.

This also creates a template. You can delete the template if you don’t need it or leave it. I got the following output when creating UserImageController


created: src/Controller/UserImageController.php
created: templates/user_image/index.html.twig
Categories
rants

Ubuntu 20 slow and freezes often

I’ve used Ubuntu since version 8 or about 2009/2010ish. It used to be super fast. You used to be able to run it on ANYTHING. It was easy to use. I can’t take the damn slowness these days.

Update. Apparently Gnome is the culprit plus some other useless crap. Some say Kubuntu is faster. I don’t have time to dink around with this right now.

Here is a total list of all the things that still suck on ALL linux desktops in 2022 Major Linux problems on the desktop 2022 edition.

These days Ubuntu is SLOWER THAN MOLASSES IN THE WINTER IN SIBERIA. So slow I am HONESTLY thinking about switching back to Windows just so I can work. I rather spend my time working than investigating and typing utter bullshit cryptic commands into the command line.

nothing works meme
Ubuntu 20

Booting is extra slow, 3 to 5 minutes on an i7 with 16Gb ram. That is purely insane. I think this is a record honestly.

Opening any program is slower than hell, whether it is a browser or an IDE, Slow. Slow and slow.

CONSTANT CRASHES AND FREEZES.

Linux has a special trick called freeze…

And once this MOFO freezes, you can’t do shit. Nothing works.  No combination of keys on the keyboard will give any result. Your only option with Ubuntu/Linux desktop is to un-plug your PC.

ISN’T THAT JUST SUPER….

I don’t feel like digging into any files to figure out WTF this pile of bile is doing. I am guessing Ubuntu/Mint (this all happens on another PC I am running mint on) TOTALLY SUCKS at memory management, as the freezes almost always happen when I am using a browser. Honestly I use my computer to get shit done not to screw around playing with the command line trying to get everything working so I can feel extra smart.

That shit is for kids. In adultland we need our PC to turn on and run programs and function and shit like that.

sarcastic kid meme
DERP

But the slowness and freezing really sucks and is pushing me to move back to Windows for web development and programming. Plus most of the image editing, video editing, etc. etc. etc. software for Linux desktop is not worth the shit. Hell most of the software for linux desktop is chock full o bugs.

I sent someone my resume I created with LibreOffice they sent me an email back saying they couldn’t open it. Yay, we still have that old ass issue in 2021???

homer simpson meme
My first encounter with an app using an ORM

Then you got the damn issues with print drivers and video drivers and drivers in general. In reality you are lucky if you get a PC to run Linux desktop properly. And don’t give me this shit about flavor xxx is better. They are all built from the same damn kernel, which is what most functionality is built on.

Right now I need my computer to work. I have too much to do to stop and investigate why Linux is not working and dig through logs and google and go to forum after forum, for 2 damn days.

Screw that and soon screw Linux desktop too.

I am over the sluggishness.

Over the super hard freezes.

Over the slow startups.

Over the slow everything and piss poor memory management.

It probably doesn’t even use all 8 cores either. And to think you used to be able to run Ubuntu on tiny devices.
I bet the Linux Fanboys are having some hyperventilating butthurt right now, gonna leave me lots of comments. They never read the article, then leave the dumbest comments.

I love you guys too and I want to help you heal that butthurt.

Categories
rants

What the world was like before Corporate greed set in

What were the good ole days like?

I am not super old, just 42 years old. But yet I am old enough to remember the good ole days. The days when Employers paid better. The days when employers cared about the product they produced or service they provided. The days when employers didn’t have a hard time finding employees because they were not so damn insanely greedy.

Why does CEO pay increase but not the workers?

Can you believe that back in the day( like before the year 2000), that companies would seek out people with an interest in their field that they could train? They would look for people that had an aptitude or interest in their given field and they would hire that person. I remember as a kid they would come around to high schools and interview Seniors and many got jobs before graduating and the companies would pay for their college.

Can you believe they would even pay to move that person? Want your mind blown further????? They even used pay for your training, certifications and college and give you a book stipend and much more. I have a friend who is a CPA, he earned his CPA that very way. He signed a contract to promise to work for 2 years after getting the CPA or he had to pay them back. He stayed for the 2 years.
They used to offer medical and dental coverage…. And to further blow your mind, did you know they used to offer pensions and retirement plans???? Not just the kind where you put into. No, like they used to fully fund those things and you could add to it too if you wanted, but it was an option!!!

Todays employers are much different than they used to be.

These were the days of our parents and grandparents. The days where you worked for a company your entire life, instead of job hopping. The days when companies cared at least a tiny bit about their employees. Hell the days when a company was around more than 4 to 6 years. The days before UBER GREEED.

Employers now encourage Job hopping

These greedy companies decided they could save more money to pay the precious investors by stealing trained employees from their competitors. This in turn leads to job hopping as companies offer more and more to steal the employee.

So they switched up the paradigm. Now instead of finding good people and training them, they use recruiters to steal them from other companies. But they claim they don’t want to hire job hoppers?

The deal gets more sour


Now we as potential employees are expected to go into deep debt getting an education so that some rich CEO and investors can get their pockets padded more?
So now we get paid less(due to inflation) and we have to pay for our education too? And jobs and companies are totally not stable anymore. To make it worse we are expected to work long hours and sometimes weekends. What a wonderful deal.

If you do the math you might not be making much more than minimum wage with that expensive degree. I’ll write another article about declining college attendance. Maybe another about the lie flat movement happening now due to this realization too.

The great resignation

We employees have finally in mass received the message loud and clear from you employers. “YOU ARE VALUELESS AND REPLACEABLE, LOOK HOW RICH AND IMPORTANT I AM

It just took the Covid 19 shutdowns for people to finally realize just how sour of a deal they were getting.

Covid awakened the sleepy employees while they were laid off at home.

Many started their own small businesses or started freelancing or trading stocks or crypto currencies. Suddenly people realized that we were being used and abused and that the money was not worth it.

We realized that maybe we can work less hours and make about the same amount of money or maybe more. Which in turn gives us more time for our family life, hobbies and living in general.

Money is only one part of the story. The way employers have been treating employees has declined since globalism and recruiting started. Back in the good ole days companies wanted to hang on to their valuable trained employees so they treated them like HUMANS. Imagine that???? Being treated like a human by your employer.

Now they treat you like a paycheck, and when that check grows too big they replace you in a heart beat. Yet you are still supposed to be loyal to the company? GTFO with that is what many of us are finally saying to the GREED MONGERERS.

The great resignation

Gone are the days of employers paying employees what they are worth. Gone are the days of employers paying to train their new employees. Gone are the days an employer will help move an employee. Gone are the days of retirement plans. Gone are the days of being treated like a human by your employer.

Now employers expect you to live in the city their home office is in or you get to pay to move. Now employers don’t offer pay increases. Now employers don’t offer retirement plans. Now employers don’t pay for college.  Now employers expect you to work more than 40 hours a week and say it is part of the salary.

Maybe now you understand why employers whine and cry they can’t find any employees.

The application process sucks

How many of you have gone through the application process lately? I’ve read many similar horror stories of people sending out their resume 300 times to get like 30 interviews, 3 job offers and 1 job. I’ve read about people having to do 6 to 10 interviews with the same company. People having to do side projects to prove their abilities etc. etc.

All in a time when employers are whining and crying and bitching and moaning that they can’t find anyone?????? REALLY???WTF?

are you kidding me
Today’s employers are cheap and picky too. WTF?

If employers want good employees again, then employers are going to have to change.

US corporations are the absolute worst on the planet too. This is why no one wants to work for them anymore if they can find a way around it. There is literally a term for it “turn and burn” where they suck the life out of their employees then replace them. The TECH/STEM industry is the most horrible about this. So bad they even make you retrain your foreign h1b replacements.

 

Categories
Web Development Web Security

Faking Enumerations with Vanilla javascript

What is an Enumeration?

An Enumeration is a way to create a limited list of options to choose from.This is useful for keeping a list of field names for a form so you can use javascript to animate something for example.

Having a limited list of options is helpful so that you can eliminate bugs due to misspellings (very common in Javascript UI programming).

A limited list also helps so that you can just type and your IDE gives you suggestions to jog your memory of the available options so you don’t have to dive into code.

See the limitations section.

Javascript has no Enums yet

Javascript has no such concept as an enumerated class… yet( the keyword enum is reserved so maybe in the future). Heck it is 2021 and PHP just got Enum classes.  While it does allow class level variables they are defined in the most funky way inside the constructor with this keyword.

Uhm, wait… what?

I say funky because with most other languages you define variables at the class level, then instantiate them ( give them a starting value ) inside the constructor for example. Just be glad you don’t have to use the old syntax What does prototypical Javascript look like?

So to define class level variables in Javascript you need to do so inside the constructor using the this keyword. The reason for this is how the Javascript prototype system works.

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}
Before Javascript classes… remembering this

Now anywhere inside the class you can get or set the value by this.height or this.width. You can’t set any constants like this though. Constants have to be defined outside the class if you want to use them inside a class, in all methods/functions. You can define a constant inside a function ( aka constructor ) but it is limited to the function in scope just like the let keyword.

But what if you want something like a list of constants or values that can be used? For example I like to keep my form field id’s inside an Enum to be able to easily refer to the field I need, but how can I do this with Javascript?

The answer

The easiest way I have come up with is to NOT USE a class at all. Instead I just use a simple file with a constant set to an object with a list of values. Sure you could just make a list of constants, but there are downsides to that. For one you would need to export them in order to import and use them.

I prefer to create a constant set to a literal object value inside of a single file, like this.

const MEDIA_FORM_FIELDS_ENUM = {
    ALLOW_COMMENTS: 'allow_comments',
    COLLECTION: 'collection',
    CONTENT_RATING: 'content_rating',
    DESCRIPTION: 'description',
    HASHTAGS: 'hashtags',
    PUBLISHED: 'published',
    REUSE_TYPE: 'reuse_type',
    TITLE: 'title'
};

export {MEDIA_FORM_FIELDS_ENUM};

Note the export.

Then I use it like this in my form or form fragment in this case.

Note the import.

import {MEDIA_FORM_FIELDS_ENUM as fields} from "../enums/MediaFormFieldsEnum";

class MediaOptions {
    static getMediaOptions(mediaType) {
        let collection = fields.COLLECTION;
        let comments = fields.ALLOW_COMMENTS;
        let description = fields.DESCRIPTION;
        let hashtags = fields.HASHTAGS;
        let published = fields.PUBLISHED;
        let reuse = fields.REUSE_TYPE;
        let title = fields.TITLE;

        return `
        <div id="media-options" class="container">
            
            <div class="form-group">
             <label for="content-rating" >${mediaType} rating</label>
               <select name="content-rating" id="content-rating" class="form-control" >
                  <option selected value="rating-everyone" id="rating-everyone" >Everyone</option>
                  <option  value="rating-mature" id="rating-mature">Mature</option>
                  <option value="rating-xrated" id="rating-xrated" >Adult rated-x</option>
               </select>
            </div>
         
          <div class="form-group">
            <label for="${title}">${mediaType} Title</label>
            <input type="text" class="form-control" id="${title}" name="${title}">
          </div>
          
          <div class="form-group">
            <label for="${description}" >${mediaType} Description</label>
            <textarea rows="5" id="${description}" name="${description}" 
            placeholder="describe the image in 200 characters" class="form-control" ></textarea>
          </div>
          
          <div class="form-group">
                <label for="${hashtags}" >${mediaType} Hashtags</label>
                <textarea rows="2" id="${hashtags}" name="${hashtags}"
                 placeholder="separate hashtags with space" class="form-control" ></textarea>
          </div>
             
          <div class="form-group">
            <label for="${collection}">${mediaType} Collection</label>
            <input type="text" class="form-control" id="${collection}" name="${collection}">
          </div>
          
          <div class="form-row">Published/visible status</div>
          <div class="form-check">
            <input class="form-check-input" type="radio" name="${published}"
             id="${published}" value="published" checked>
            <label class="form-check-label" for="${published}">
             Published ( visible to others )
            </label>
          </div>
          <div class="form-check">
            <input class="form-check-input" type="radio" name="${published}"
             id="unpublished" value="unpublished">
            <label class="form-check-label" for="unpublished">
             Un-Published ( visible to only you )
            </label>
          </div>
          
          <div class="form-group">
             <label for="${comments}">Allow comments</label>
               <select name="${comments}" id="${comments}" class="form-control" >
                  <option value="followers" >Buyers only/no one/private</option>
                  <option selected value="everyone" >Everyone & Buyers</option>
                  <option value="followers" >Followers & Buyers</option>
               </select>
           </div>
             
          <div class="form-group">
             <label for="${reuse}" >Allow reuse</label>
               <select name="${reuse}" id="${reuse}" class="form-control" >
                  <option selected value="none" id="reuse-none" >None/private (me only)</option>
                  <option  value="free" id="reuse-free">Free</option>
                  <option value="credits" id="reuse-credits" >Credits</option>
               </select>
            </div>
           
        </div>
        `;
    }
}

export {MediaOptions}

That is a lot of code. Note it is HTML inside of a Javascript Literal. I’ll write another article about creating templates with Javascript literals later. For now note how I imported it and used it. I could have just called the fields.OPTIONS but that is longer than a variable name.

I use the above code by importing it into yet another file that builds a whole form but only when called. Like I said I’ll have to write an article about the Javascript Literals, because wow they are handy.

Vanilla javascript might be a little more work, but in the end when something doesn’t work you know exactly why and exactly where to look. And if it is a bug… IT IS YOUR BUG and you can quickly fix it and move right along.

Limitations

Javascript is a real screwy language with lots of limitations and quirks. Many don’t make a lick of sense, unless you have the unlimited free time to dig deeply into the internet to dig deeply into how the Interpreter works.

The above was working fine… until I tried to use it in an Object. When I try to use something like

let stupidObj = { ImageDataEnum.ALLOW_CONTENT_RATING_CHANGE : 'no', 'test' : 'value' };

But this does not work. You will get an error of some type or another. The error I got was something like ” , found expecting ; ” and then a lot of lines of barfarony.

Javascript interpreter was all like…

baby threw up meme
The limitations are real.

So I figured ok maybe store it in a variable and then try that.

const tester = ImageDataEnum.ALLOW_CONTENT_RATING_CHANGE;
let stupidObj = { tester : 'no', 'test' : 'value' };

That doesn’t work either. The interpreter doesn’t interpret the const tester as the tester I wanted to use as the object key so it outputs something different.
The idea here is I want to store all of the key names in a constant and use them later to build an object. This way I for sure know I will spell the key names correctly and I don’t have to remember them all, my IDE can show me the list.  This way I can request some data with AJAX and set the values for an object, then use the object to manipulate the values or use them.

Using the fake enums like this works perfectly fine.


import {JsCollection} from "./JsCollection";
import {ImageDataEnum} from "../enums/ImageDataEnum";
import {Utils} from "./Utils";

class ImageData {

    constructor(imageData) {
        this.imageData = new JsCollection();
        this.setValues(imageData);
    }

    getAllowComments(){
        return this.imageData.getElementValue(ImageDataEnum.ALLOW_COMMENTS);
    }

    getAllowResale(){
        return this.imageData.getElementValue(ImageDataEnum.ALLOW_RESALE);
    }

    getAllowContentRatingChange(){
        return this.imageData.getElementValue(ImageDataEnum.ALLOW_CONTENT_RATING_CHANGE);
    }

    getAltText(){
        return this.imageData.getElementValue(ImageDataEnum.ALT_TEXT);
    }

    getCollectionName(){
        return this.imageData.getElementValue(ImageDataEnum.COLLECTION_NAME);
    }

    getContentRating(){
        return this.imageData.getElementValue(ImageDataEnum.CONTENT_RATING);
    }

    getCreationDatetime(){
        return this.imageData.getElementValue(ImageDataEnum.CREATION_DATETIME);
    }

    getDescription(){
        return this.imageData.getElementValue(ImageDataEnum.DESCRIPTION);
    }

    getFileUrl(){
        return this.imageData.getElementValue(ImageDataEnum.IMAGE_FILE_URL);
    }

    getHashTags(){
        return this.imageData.getElementValue(ImageDataEnum.HASHTAGS);
    }

    getImageFileData(){
        return this.imageData.getElementValue(ImageDataEnum.IMAGE_DATA_FILE);
    }

    getImageId(){
        return this.imageData.getElementValue(ImageDataEnum.IMAGE_ID);
    }

    getLanguageCode(){
        return this.imageData.getElementValue(ImageDataEnum.LANGUAGE_CODE);
    }

    getLastEditDatetime(){
        return this.imageData.getElementValue(ImageDataEnum.LAST_EDIT_TIMESTAMP);
    }

    getPublishedStatus(){
        return this.imageData.getElementValue(ImageDataEnum.PUBLISHED_STATUS);
    }

    getTitle(){
        return this.imageData.getElementValue(ImageDataEnum.TITLE);
    }

    getVisibility(){
        return this.imageData.getElementValue(ImageDataEnum.VISIBILITY);
    }

    setAllowComments(allowComments){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.ALLOW_COMMENTS, allowComments);
    }

    setAllowResale(allowResale){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.ALLOW_RESALE, allowResale);
    }

    setAltText(altText){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.ALT_TEXT, altText);
    }

    setCollectionName(collectionName){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.COLLECTION_NAME, collectionName);
    }

    setContentRating(contentRating){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.CONTENT_RATING, contentRating);
    }

    setCreationDatetime(datetime){

    }
    setDescription(description){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.DESCRIPTION, description);
    }

    setHashtags(hashtags){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.HASHTAGS, hashtags);
    }

    setLastEditDatetime(datetime){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.LAST_EDIT_TIMESTAMP, datetime);
    }
    setImageId(id){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.IMAGE_ID, id);
    }

    setImageFileData(fileData){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.IMAGE_DATA_FILE, fileData);
    }

    setImageFileUrl(fileUrl){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.IMAGE_FILE_URL, fileUrl);
    }

    setLanguageCode(languageCode){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.LANGUAGE_CODE, languageCode);
    }

    setPublishedStatus(publishedStatus){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.PUBLISHED_STATUS, publishedStatus);
    }

    setTitle(title){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.TITLE, title);
    }

    /**
     * used internally to set the imageData values, but JS sucks and has no
     * idea what private or protected is
     * @param imageData
     */
    setValues(imageData){

        this.setAllowComments(Utils.getArrayValue(imageData, ImageDataEnum.ALLOW_COMMENTS, ''));
        this.setAllowResale(Utils.getArrayValue(imageData, ImageDataEnum.ALLOW_RESALE, ''));
        this.setAltText(Utils.getArrayValue(imageData, ImageDataEnum.ALT_TEXT, ''));
        this.setCollectionName(Utils.getArrayValue(imageData, ImageDataEnum.COLLECTION_NAME));
        this.setContentRating(Utils.getArrayValue(imageData, ImageDataEnum.CONTENT_RATING, ''));
        this.setCreationDatetime(Utils.getArrayValue(imageData, ImageDataEnum.CREATION_DATETIME, ''));
        this.setDescription(Utils.getArrayValue(imageData, ImageDataEnum.DESCRIPTION, ''));
        this.setHashtags(Utils.getArrayValue(imageData, ImageDataEnum.HASHTAGS, ''));
        this.setImageId(Utils.getArrayValue(imageData, ImageDataEnum.IMAGE_ID, ''));
        this.setImageFileData(Utils.getArrayValue(imageData, ImageDataEnum.IMAGE_DATA_FILE, ''));
        this.setImageFileUrl(Utils.getArrayValue(imageData,ImageDataEnum.IMAGE_FILE_URL, ''));
        this.setLanguageCode(Utils.getArrayValue(imageData, ImageDataEnum.LANGUAGE_CODE, ''));
        this.setLastEditDatetime(Utils.getArrayValue(imageData, ImageDataEnum.LAST_EDIT_TIMESTAMP, ''));
        this.setPublishedStatus(Utils.getArrayValue(imageData, ImageDataEnum.PUBLISHED_STATUS, ''));
        this.setTitle(Utils.getArrayValue(imageData, ImageDataEnum.TITLE, ''));
    }

}

export {ImageData};

Using the above I can request an images data from the server using AJAX. Then I pass the JSON Object, which was a PHP multidimensional array with keys and values converted to JSON, to this setValues() method.

I can then call the getters to get a value, it will either be a value or an empty string if nothing existed.

With this setup I can set default values when none exist. I also have enums which hold the values I will add as defaults to this later.

So as you can see this is highly useful. This way I can use the Enum anywhere I need a string and I don’t have to worry about spelling. If I do misspell something, I change it one time in one location and I am done. Otherwise I’d have to hunt down all the locations in the text etc. where I had hard coded a value.

But the limitation is Javascript is like WTF is this when you try to use them in Object literals. This is the only place I have found that they don’t work so far.

Dictating like…

It seems like the Javascript interpreter is trying to call a function when it sees the Enum constant reference inside an object literal.  But the rest of the time it works 100% fine. I only wanted this for testing so I’ll move on and not try using it inside object literals for testing LOL.

The work around

So I kept digging until I found a work around, a way to keep using my Fake enums and be able to build an object using the enums as the key names.

You need to use Object.defineProperty() to set a propertyusing the Enum and you get it back using Object.getOwnProperty(). I’ll update this article later when I have the time .

Links

Mozilla Developer Network Javascript class info

Mozilla Developer Network Javascript const info

Mozilla Developer Network javascript literal info

While working on this article I found this excellent article about using Enums in Javascript.

Categories
Software Development Web Development

Php Backed Enums don’t forget to call value

This is about the change from the old way of doing things to the new Enum classes.  I’m currently working to switch over from the old way to the new way, one file at a time.

This means in places where I refactor code I have to remember to call ->value. Hence the article title “Php Backed Enums don’t forget to call value”

Well thanks to my IDE PhpStorm, I caught this error before it happened to me… in most places.

I like the concept of having an Enum class as up until version PHP 8.1 you had to create class constants and pretend they were real Enums.

Old php enums

Old PHP Enums Example

Here is how we used to do PHP Enums for forever until version 8.1

class ImageDataEnum
{
    const HEIGHT = 'height';
    const SIZE_STRING = 'size';
    const IMAGE_URL = 'image_url';
    const WIDTH = 'width';
}

And to use that in any code you simply did the following where you needed a value.

$height = ImageDataEnum::HEIGHT;

And inside $height would be the string “height” you could use this to make sure a value exists without having to spell it out every time, reducing the likelihood of bugs. This is very straight forward and easy. You can still add constants to Enum classes and use them, but it feels better using case instead.

New Enums

A backed enum looks like this. Note const is now case, class is now enum, but the rest is about the same.

enum ImageDataEnum: string
{
    case HEIGHT = 'height';
    case SIZE_STRING = 'size';
    case IMAGE_URL = 'image_url';
    case WIDTH = 'width';
}

Notice the word “string” you can use int or string but not a combination of both. Backed Enums Docs here.

Now to use the new Enums like the code above you do like this

$height = ImageDataEnum::HEIGHT->value;

Otherwise $height will be an object, one that contains  handy built in methods try() and tryFrom(). See the doc links for more info on that. You can also define your own methods.

But if you fail to call ->value and you try to use this for a string comparison you will get oopsies. You can use the IDE to hunt down all cases of the old class type enums.

//this won't work
if('height' === ImageDataEnum::HEIGHT ){
 //code to do stuff in here
}

The above will result in an error telling you the comparison is not possible. You can’t compare a string to an object.

//this will work
if('height' === ImageDataEnum::HEIGHT->value ){
 //code to do stuff in here
}

You can also call ImageDataEnum::HEIGHT->name which will return HEIGHT. So you can get the name and value using those methods.

Another nice thing about the new Enum classes is they are full on classes, you can add methods to them if you want. Like checking if a value matches any of the case values or whatever your use case is.

Enums are really handy for limiting what values can be entered by users and checking against them. Another good use I have found is creating a list of options for a Database table column.

Here is an example of a column in one of my tables that stores a medias content rating type. The system later uses this in many places to make sure that the media is of this type or that the user wants to see this type of media.

enum ContentRatingsEnum: string
{
    case EVERYONE = 'everyone';
    case MATURE = 'mature';
    case RATED_X = 'rated-x';
}

This column in a media table can only contain these values and users can only select from these values as their content preference type. This is helpful because I don’t have to type those strings in 100,000 places and when I need to change one I simply refactor with my IDE features.

Here is an excellent video that just came out about PHP ENUM’s the start of the video is anyways.