Categories
Uncategorized Web Development

Why most current social platforms suck

One major problem with most current social platforms is they randomly ban people for things that are not offensive.

You can’t even make a joke on these platforms. They ban people for making jokes like the following.

Social censorship at it’s finest

Anyone with an IQ over 75 can clearly see I am making a joke here about how much Javascript sucks. In no way am I telling anyone to harm themselves or saying I want to harm myself.

What kind of petty broken censorship system is this?  Is this AI making these judgements? If so it sucks.
Did  some amateur that just started to learn to code, who is totally in love with Javascript reported this? If so grow up and learn some other languages and you will realize Javascript sucks.

I use Javascript all day every day for user interface creation, which is probably why I hate it. It just swallows things that should be an error or warning, making debugging harder. The 100% lack of a type system makes it really suck BAD BAD BAD. You never know what is going to be passed to functions. This. Don’t get me started on this in Javascript.
Javascript was one of the first languages I learned 15 years ago now. I have plenty experience with Javascript. I also have used Golang, PHP, Perl, Java, Scala, Actionscript, VB.NET, Sketchs, BASH and more. I speak out of experience not failure.

And if you are a Javascript fanboy and don’t know why it sucks… read this article. Javascript gives you secret hidden bugs and errors.

So much suck in 1 language

 

Categories
Software Development Web Development

How to get URL Routes in your Javascript in Symfony 5+

WARNING :  The current docs that were in the symfony docs are 404 right now. The only documentation is found in the library itself. I’ll review it and rewrite this article once I get it working.

so shocked

This is something I needed to know how to do so I could generate URLs inside my javascript more easily.

Currently the docs on this are outdated so I will record what I did here so I can know later, or for others who need this info.

The docs say something about app/appKernel.php that file no longer exists.  It has been replaced with just kernel.php and you do pretty much nothing to that file.

I am not sure what version Symfony started this but I know in 5+ you don’t have to register the bundle like step 2 of the old docs shows. This is done automatically for you in a new file named config/bundles.php which is where all bundles are automatically registered when you install them.

So all you do is this command now in the terminal

composer require friendsofsymfony/jsrouting-bundle

inside your apps root level. It is auto registered for you.

You don’t have to do step 3 of the old docs either as the config system has changed. There is no

app/config/routing.yml

file anymore. When you install the bundle the new system creates a new file named app/config/routes/fos_js_routing.yaml for you
For step 4 the required code has changed you need to enter this now.

$ php bin/console assets:install --symlink public

Basically to install the bundle you just use the composer command and most of the stuff is now done for you.

A silent secret.

In order for the routes to work you must add something new to every single route definition you want to be able to generate a route in your javascript for.

You must add this to the annotation or where ever you define your routes. I use annotations so I can just look to the controllers, plus when I split the system into microservices the routes go with their services.

options={"expose"=true}

So for my menu route the definition in the controller looks like this.

@Route("/menu", name="menu", options={"expose"=true}, methods={"GET"})
</code.

Now how to use it?

So now that it is installed how do you use it? One more step. Now you must include the needed Javascript in your page with these tags.

<script src="{{ asset('bundles/fosjsrouting/js/router.js') }}"></script>
<script src="{{ path('fos_js_routing_js', { callback: 'fos.Router.setData' }) }}"></script>

Place those anywhere you want. What I did is put them in my base twig template in a section where I include my Javascript this way it is available on every page because I will probably be needing access to routes as I build the app.

Once you have all of the above done you can use it like this

let testUrl = Routing.generate( 'menu');
console.log(testUrl); //outputs /menu

Route not found errors?

Yes I got these too. You must add expose

options={"expose"=true}

to every single route you want to use. And your IDE probably won’t be very helpful so double check the spellings. Routing probably won’t be found by your IDE.

Here is a link to my favorite HTMLEntities converter if you write articles about programming you will need this.

 

Categories
Software Development Web Development

How to create and use a custom Javascript Event

You have probably used events in Javascript many times.  Especially if you have done any User Interface programming. There are many types of events provided by browsers and the Javascript engines.

Did you know you can create your own custom events with the CustomEvent() constructor? Here is a minimal example. In this article I will explain custom events and how to use them.

First off what would you use a custom event for? You use them to notify other objects in your app that actions have occurred such as “user clicked x” or “user closed dialog”. Using events prevents code coupling and reduces dependencies.

I came across this need when creating a dialog box where I wanted an overlay to show beneath it blocking out the page behind. I didn’t want my DialogBox to have to know about my Overlay.  I didn’t want them coupled. I didn’t want my DialogBox to have to have an Overlay object as an argument creating a dependency. I didn’t want my DialogBox to even know that an Overlay object existed.

The answer is for my DialogBox to emit/create/dispatch a custom Javascript Event and have a listener for that event to close the Overlay.

So lets look at some code.


import {Utils} from "./Utils";

class DialogBox {

    /**
     *
     * @param {string} dialogId
     * @param {string} dialogClass
     */
    constructor(dialogId = 'dialogId', dialogClass = '') {
        this.closeDialogId = 'closeDialog';
        this.dialogBoxBottomRowId = 'dialogBoxBottomRow';
        this.dialogBoxClass = 'dialogBox ' + dialogClass;
        this.contentContainerId = 'dialogContentContainer';
        this.dialogHtml = '';
        this.dialogBoxId = dialogId;
        this.closeHandler = null;
        this.divElement = null;
    }

    /**
     *
     * @param {string} rowContent : the content HTML etc to be added as a row
     * @param {string} cssClass : applied only if passed in
     * @returns {void}
     */
    bottomRow(rowContent = '', cssClass = 'dialog-bottom-row') {

        let bottomRow = document.getElementById(this.dialogBoxBottomRowId);

        //if the bottom row does not exist add it to the html
        if ( Utils.isEmpty(bottomRow)) {
            let rowHtml = '<div id="' + this.dialogBoxBottomRowId + '" ';
            rowHtml += ' class="' + cssClass + '" ';
            rowHtml += ' >' + rowContent + '</div>';
            this.dialogHtml += rowHtml;
        } else {
            //if a bottom row exists replace it
            bottomRow.className = cssClass;
            bottomRow.innerText = rowContent;
        }
    }
    /**
     * Centers the dialog vertically and horizontally in the parent element
     * @param {string} parentElementId
     */
    centerDialog(parentElementId = 'body') {

        let parentWidth = 0;
        let parentHeight = 0;
        //need the dialog boxes calculated width and height
        let dialogHeight = this.divElement.clientHeight;
        let dialogWidth = this.divElement.clientWidth;

        //must use two different ways to get the height and width
        if (parentElementId === 'body') {
            parentWidth = window.innerWidth;
            parentHeight = window.innerHeight;
        } else {
            let parentElement = document.getElementById(parentElementId);
            //make sure null or undefined were not returned
            if (!Utils.isEmpty(parentElement)) {
                parentHeight = parentElement.clientHeight;
                parentWidth = parentElement.clientWidth;
            }
        }
        let left = (parentWidth / 2) - (dialogWidth / 2);
        let top = (parentHeight / 2) - (dialogHeight / 2);
        //must add px or it doesn't work at all
        this.divElement.style.top = top + 'px';
        this.divElement.style.left = left + 'px';
    }
    /**
     *
     * @param {string} content
     * @param {string} containerClass
     */
    contentContainer(content, containerClass = 'dialog-content') {
        let contentDiv = document.getElementById(this.contentContainerId);

        //if the container exists replace it contents
        if (Utils.isEmpty(contentDiv)) {
            let contentHtml = '<div id="' + this.contentContainerId + '"';
            contentHtml += ' class="' + containerClass + '" >';
            contentHtml += content + '</div>';
            this.dialogHtml += contentHtml;
        } else {
            contentDiv.innerHTML = content;
            contentDiv.className = containerClass;
        }
    }
    /**
     * calls removeDialogBox which removes the dialog and event listeners
     */
    hideDialogBox() {
        this.removeDialogBox();
    }

    /**
     *
     * @param {string} menuText
     * @param {string} menuTextClass
     */
    menuBar(menuText, menuTextClass = '') {
        let menuTextId = 'dialog-menu-text';
        /*
         * if dialogMenuBar is present then the length will be non zero or true
         * if this is the case replace the content, this allows this method to be called
         * again later to change the value
         */
        let menuBarID = 'dialog-menu-bar';
        let menuTextDiv = document.getElementById(menuTextId);

        if (Utils.isEmpty(menuTextDiv)) {
            this.dialogHtml = '<div class="dialog-menu-bar" id="dialog-menu-bar" >';
            this.dialogHtml += '<div id="' + menuTextId + '" class="dialog-menu-text ';
            this.dialogHtml += menuTextClass + '" >' + menuText + '</div>';
            this.dialogHtml += '<div id="' + this.closeDialogId + '" class="close-dialog" >';
            this.dialogHtml += '<img src="/images/drawing/close-window.png" ';
            this.dialogHtml += 'alt="Close dialog" >';
            this.dialogHtml += '</div></div>';
        } else {
            menuTextDiv.innerText = menuText;
            menuTextDiv.className = menuTextClass;
        }
    }

    /**
     * displays the dialog box, you must call centerDialog to center it
     */
    showDialogBox() {
        //remove any existing dialog boxes first
        this.removeDialogBox();
        this.divElement = document.createElement("div");
        this.divElement.id = this.dialogBoxId;
        this.divElement.className = this.dialogBoxClass;
        this.divElement.innerHTML = this.dialogHtml;
        //position the dialog box now give it the highest z-index to be on top
        this.divElement.style.zIndex = Utils.getHighestZIndex() + 1;
        document.body.appendChild(this.divElement);
        //add the listener for when the user clicks to close

        this.closeHandler = function ( ) {
        this.removeDialogBox();
        }.bind(this);

        let close = document.getElementById(this.closeDialogId);
        close.addEventListener('click', this.closeHandler, false);
    }

    /**
     * removes the dialog html from the page and removes the close listener
     * dispatches event 'dialogClosed' to be used to close an overlay etc.
     */
    removeDialogBox() {
        let dialogElem = document.getElementById(this.dialogBoxId);
        //if a dialog box of the same id exists delete it first to prevent errors and issues
        if (dialogElem) {
            let close = document.getElementById(this.closeDialogId);
            close.removeEventListener('click', this.closeHandler, false);
            dialogElem.remove();
            const dialogEvent = new CustomEvent('dialogClosed');
            document.body.dispatchEvent(dialogEvent);
        }
    }
}

export {DialogBox}

That is a lot of code 169 lines to be exact. I am still in the process of converting this code, still going to add some Template literals instead of the old fashioned string concatenation technique.

There are several very import things to note here in this code. For example the way the addEventListener() and removeEventListener are used. These functions have to be passed THE EXACT SAME parameters or removeEventListener() fails to remove the event listener and that clutters your memory up because you will have listeners referring to elements that don’t exist.

That is why I have this code this.closeHandler


//add the listener for when the user clicks to close

        this.closeHandler = function ( ) {
        this.removeDialogBox();
        }.bind(this);

        let close = document.getElementById(this.closeDialogId);
        close.addEventListener('click', this.closeHandler, false);

See this.closeHandler = function  that stores the function to handle the click on the close button. Both the add and remove event listener functions have to be passed the exact same function.

Look at the removeDialogBox function closer.


let dialogElem = document.getElementById(this.dialogBoxId);
        //if a dialog box of the same id exists delete it first to prevent errors and issues
        if (dialogElem) {
            let close = document.getElementById(this.closeDialogId);
            close.removeEventListener('click', this.closeHandler, false);
            dialogElem.remove();
            const dialogEvent = new CustomEvent('dialogClosed');
            document.body.dispatchEvent(dialogEvent);
        }

Notice that the second argument to removeEventListener is this.closeHandler that is the same function passed to the addEventListener above.

If you use anonymous functions inside add and remove event listeners instead, then they wont be the same function and so your event listener won’t be removed and your memory fills up faster.

Another important note is that the this.handler function must use bind(this) like so

this.closeHandler = function ( ) { this.removeDialogBox(); }.bind(this);

If you don’t bind the function expression then you will get an error about this.removeDialogBox is not a function.
This is because you are storing the closeHandler in memory for later use.  At that later time the context will be different, the code won’t be executing within your class anymore, it will be in it’s own context. That means “this” that was alive in your class, no longer exists. Which means that function no longer exists You must bind “this” by using “.bind(this) at the end of the function.

And now about the Custom Event. You will see it at the bottom of the removeDialogbox() function

const dialogEvent = new CustomEvent('dialogClosed');
document.body.dispatchEvent(dialogEvent);

Those two lines is all it takes to create and dispatch your own Custom event. This means you write code that listens for the custom “dialogClosed” event to be fired like this.

document.body.addEventListener('dialogClosed', function (){
overlay.hideOverlay();
});

Notice I am using document.body this is a very easy way to create the listener. This is using an anonymous function which is bad because this listener can’t be removed. It should be removed right below this. In order to do that you would need to create the handler function above it and pass it to both the add and remove event listeners.

Adding information to the event.
This is one of the most handy parts of custom events, the ability to pass information in the event. This can be any information, a full object even.
In this article it mentions adding custom data with “detail”.  Here is an example from the code above.

const dialogEvent = new CustomEvent('dialogClosed',{ detail: {
        id: this.dialogBoxId
    } });

Here I am passing id you can use this same format to pass many more values just add a comma to the end of each one. Then to access the extra information in your listener you do like this.

document.body.addEventListener( 'dialogClosed', function (event) {

let dialogId = event.detail.id
switch (dialogId) {
case mainDialogId :
mainOverlay.hideOverlay();
break;
case colorDialogId :
overlay2.hideOverlay();
break;
}

}, false);

Above I am using event.detail.id to get the value I stored in id in the detail of the custom event. Notice how I am using a switch statement to compare the id’s of the dialog that closed to close the correct overlay. There is no default behavior for this action, either one dialog closes or another. I could have 5 different dialogs if I wanted.