Categories
Software Development Web Development

My symfony command line cheat sheet

I can never remember all the symfony commands. So I will add them here one at a time as I need them to create a cheat sheet.

Cache – clearing the cache explained

Clear all the caches

php bin/console cache:pool:clear cache.global_clearer

 

Categories
Software Development Web Development

Symfony 5+ using the command line and listing commands

Symfony has useful command line commands. But what are the available commands and how do you get a list?

To get started type the following in the command line in your projects main/root folder. This is the directory that contains the directories src,assets, bin, public, node_modules etc.
php bin/console -help
That command will output the following

bin console help
Output of php bin/console -help command

This shows we can use bin/console list and a few more. Now try the command and see what the list shows.


Available commands:
  about                                      Displays information about the current project
  help                                       Displays help for a command
  list                                       Lists commands
 assets
  assets:install                             Installs bundles web assets under a public directory
 cache
  cache:clear                                Clears the cache
  cache:pool:clear                           Clears cache pools
  cache:pool:delete                          Deletes an item from a cache pool
  cache:pool:list                            List available cache pools
  cache:pool:prune                           Prunes cache pools
  cache:warmup                               Warms up an empty cache
 config
  config:dump-reference                      Dumps the default configuration for an extension
 dbal
  dbal:run-sql                               Executes arbitrary SQL directly from the command line.
 debug
  debug:autowiring                           Lists classes/interfaces you can use for autowiring
  debug:config                               Dumps the current configuration for an extension
  debug:container                            Displays current services for an application
  debug:event-dispatcher                     Displays configured listeners for an application
  debug:form                                 Displays form type information
  debug:router                               Displays current routes for an application
  debug:translation                          Displays translation messages information
  debug:twig                                 Shows a list of twig functions, filters, globals and tests
  debug:validator                            Displays validation constraints for classes
 doctrine
  doctrine:cache:clear-collection-region     Clear a second-level cache collection region
  doctrine:cache:clear-entity-region         Clear a second-level cache entity region
  doctrine:cache:clear-metadata              Clears all metadata cache for an entity manager
  doctrine:cache:clear-query                 Clears all query cache for an entity manager
  doctrine:cache:clear-query-region          Clear a second-level cache query region
  doctrine:cache:clear-result                Clears result cache for an entity manager
  doctrine:database:create                   Creates the configured database
  doctrine:database:drop                     Drops the configured database
  doctrine:database:import                   Import SQL file(s) directly to Database.
  doctrine:ensure-production-settings        Verify that Doctrine is properly configured for a production environment
  doctrine:fixtures:load                     Load data fixtures to your database
  doctrine:mapping:convert                   [orm:convert:mapping] Convert mapping information between supported formats
  doctrine:mapping:import                    Imports mapping information from an existing database
  doctrine:mapping:info                      
  doctrine:migrations:current                [current] Outputs the current version
  doctrine:migrations:diff                   [diff] Generate a migration by comparing your current database to your mapping information.
  doctrine:migrations:dump-schema            [dump-schema] Dump the schema for your database to a migration.
  doctrine:migrations:execute                [execute] Execute one or more migration versions up or down manually.
  doctrine:migrations:generate               [generate] Generate a blank migration class.
  doctrine:migrations:latest                 [latest] Outputs the latest version
  doctrine:migrations:list                   [list-migrations] Display a list of all available migrations and their status.
  doctrine:migrations:migrate                [migrate] Execute a migration to a specified version or the latest available version.
  doctrine:migrations:rollup                 [rollup] Rollup migrations by deleting all tracked versions and insert the one version that exists.
  doctrine:migrations:status                 [status] View the status of a set of migrations.
  doctrine:migrations:sync-metadata-storage  [sync-metadata-storage] Ensures that the metadata storage is at the latest version.
  doctrine:migrations:up-to-date             [up-to-date] Tells you if your schema is up-to-date.
  doctrine:migrations:version                [version] Manually add and delete migration versions from the version table.
  doctrine:query:dql                         Executes arbitrary DQL directly from the command line
  doctrine:query:sql                         Executes arbitrary SQL directly from the command line.
  doctrine:schema:create                     Executes (or dumps) the SQL needed to generate the database schema
  doctrine:schema:drop                       Executes (or dumps) the SQL needed to drop the current database schema
  doctrine:schema:update                     Executes (or dumps) the SQL needed to update the database schema to match the current mapping metadata
  doctrine:schema:validate                   Validate the mapping files
 fos
  fos:js-routing:debug                       Displays currently exposed routes for an application
  fos:js-routing:dump                        Dumps exposed routes to the filesystem
 lint
  lint:container                             Ensures that arguments injected into services match type declarations
  lint:twig                                  Lints a template and outputs encountered errors
  lint:xliff                                 Lints a XLIFF file and outputs encountered errors
  lint:yaml                                  Lints a file and outputs encountered errors
 make
  make:auth                                  Creates a Guard authenticator of different flavors
  make:command                               Creates a new console command class
  make:controller                            Creates a new controller class
  make:crud                                  Creates CRUD for Doctrine entity class
  make:docker:database                       Adds a database container to your docker-compose.yaml file
  make:entity                                Creates or updates a Doctrine entity class, and optionally an API Platform resource
  make:fixtures                              Creates a new class to load Doctrine fixtures
  make:form                                  Creates a new form class
  make:message                               Creates a new message and handler
  make:messenger-middleware                  Creates a new messenger middleware
  make:migration                             Creates a new migration based on database changes
  make:registration-form                     Creates a new registration form system
  make:reset-password                        Create controller, entity, and repositories for use with symfonycasts/reset-password-bundle
  make:serializer:encoder                    Creates a new serializer encoder class
  make:serializer:normalizer                 Creates a new serializer normalizer class
  make:subscriber                            Creates a new event subscriber class
  make:table-meta                            Creates classes with table data
  make:test                                  [make:unit-test|make:functional-test] Creates a new test class
  make:twig-extension                        Creates a new Twig extension class
  make:user                                  Creates a new security user class
  make:validator                             Creates a new validator and constraint class
  make:voter                                 Creates a new security voter class
 reset-password
  reset-password:remove-expired              Remove expired reset password requests from persistence.
 router
  router:match                               Helps debug routes by simulating a path info match
 secrets
  secrets:decrypt-to-local                   Decrypts all secrets and stores them in the local vault
  secrets:encrypt-from-local                 Encrypts all local secrets to the vault
  secrets:generate-keys                      Generates new encryption keys
  secrets:list                               Lists all secrets
  secrets:remove                             Removes a secret from the vault
  secrets:set                                Sets a secret in the vault security
  security:encode-password                   Encodes a password
  server
  server:dump                                Starts a dump server that collects and displays dumps in a single place
  server:log                                 Starts a log server that displays logs in real time
 translation
  translation:update                         Updates the translation file

Wow that is a lot of commands. Enter the list command in your terminal to get a better view.

mr rodgers how it is done meme
And that is how it is done!!!
Categories
Web Development

How fix doctrine error : You are missing a “cli-config.php” or “config/cli-config.php” file in your project, which is required to get the Doctrine Console working.

So while following this guide in the documentation to try to reverse engineer my database with Doctrine, I got the following 100% absolutely nonsensical error

Importing mapping information from "default" entity manager
 writing src/Entity/AffiliateArticleViews.php
 writing src/Entity/AffiliateJoinCounts.php
 writing src/Entity/AffiliateRegistration.php
 writing src/Entity/BlogMedia.php
 writing src/Entity/CommentReplyConclusionOptions.php
 writing src/Entity/CommentReplyReportReasons.php
 writing src/Entity/DoctrineMigrationVersions.php
 writing src/Entity/FavoredImages.phP

In DebugClassLoader.php line 346:

Warning: include(/var/www/sogi/sogizmo/vendor/composer/../../src/Entity/FavoredImages.php): failed to
open stream: No such file or directory

To someone who doesn’t work on the Symfony core that might as well say “boasdfkajsdfiioaysdfiuasdfiouasdfiouasdiofu”

So I had to research this issue.

Doing Research!!!

Ok so I found this link with a little more info and I ran the following line of code.

php vendor/bin/doctrine orm:generate-entities --help

Which barfed out these lines of more mysteries to be solved.

You are missing a "cli-config.php" or "config/cli-config.php" file in your project, which is required to get the Doctrine Console working. You can use the following sample as a template:

<?php use Doctrine\ORM\Tools\Console\ConsoleRunner; // replace with file to your own project bootstrap require_once 'bootstrap.php'; // replace with mechanism to retrieve EntityManager in your app $entityManager = GetEntityManager(); return ConsoleRunner::createHelperSet($entityManager); </code

Leaving me wondering WTF is this even talking about like…

Who comes up with this?

Google and dig as I may I only found one Stack answer that explained how to created the mess of files required. Sure would be nice if that was in the Doctrine docs…

But I kept digging, trying, failing… and it turns out that the first error I got means…. wait for it…. MY USER DOCTRINE IS RUNNING AS CAN’T WRITE TO THE DIRECTORY.  Now wouldn’t it make much more sense to give that as an error message?

MAKES PERFECT SENSE
MAKES ABSOLUTE PERFECT SENSE

So I changed the Entity directory permissions to 775 since my server www-data was already a member of a group which was the group owner of that directory. And the first command finally worked like it had before. Some how somewhere along the way I set permissions on the Entity and all folders to 755.

The error messages were no help. I just randomly decided to try creating another test folder with 775 and ran the first command again. I accidentally guessed what the problem was from a nonsensical error message. Desperation wins in the problem solving game. LOL

That wasn’t the end though. When I tried to run the second command I got another nonsensical error about my Entity classes already exist. I was like I know the first command created them WTF. Kept googling and searching and scratching my head. Then I remembered I created the test folder so I deleted it and BOOM BANG POW. Everything was working fine.

i m stupid meme
Im is stupid
Categories
Web Development

WordPress : Update failed: The update cannot be installed because we will be unable to copy some files. This is usually due to inconsistent file permissions.

So you got this error when trying to update one of your plugins or upload an image or something else?

I was confused by this message right after creating a new wordpress website and then migrating my content. The migration seemed to work fine. All my articles appeared. All my images appeared. I felt a I had achieved great success.

borat great success meme
I had a great success. NOT!!!

Then I tried to write an article and add an image. I got a failed to upload image message. The odd thing is I could go to old articles and still upload images.

WTF is going on here
So why doesn’t this work?

I thought that was weird. Then I got a notice in my dashboard that a plugin needed updating. I tried to update it and got the error that lead you hear.

I was lazy and ignored it a few days, thought it was just the plugin. Then another plugin needed updating. When I tried to update it I got the error again.

Update failed: The update cannot be installed because we will be unable to copy some files. This is usually due to inconsistent file permissions.

I’ve done a lot of server admin work so I figured it was a simple permission issue. The only problem I had no idea what directories/folders needed permission changes and what they needed to be.

So I did a little research.

Doing Research!!!

First update the user permissions with usermod. After this your files owner and group will be something like www-data that is what my server (Nginx) was named. How to name it is way beyond this article.

sudo usermod -aG www-data $USER

If you need to know where your wordpress is installed type the following using find command again.

find -name wordpress

That command will output something like /var/www/html/…/…/…/wordpress  It finds anything with the name wordpress.

Next set the permissions on the files under your wordpress directory. Mine was located at /var/www/html/ so I used.

find /var/www/html/ -type f -exec chmod 664 {} \;

This uses the find Linux command and exec with chmod command. Basically this line of code is using the find command to find all the files (f) in the directories in or below /var/www/html/ and it applies chmod to them with permissions of 664.

Next you need to change the directory permissions so that wordpress plugins can write to them and images can be uploaded etc. This will use the find command too, but slightly different syntax. This time use a d to find the directories in or below /var/www/html/ like so.

find /path/to/site/ -type d -exec chmod 775 {} \;

That should make your wordpress work. Your site may be located in a directory different from /var/www/html/ you will need to look in your Server (nginx/apache) settings for the root directory or use the find -name wordpress command shown. That is where the permissions need changing.

More about using exec with find

Categories
Software Development Web Development

How to fix Github git keeps asking for password with ssh keys

This was a super annoying issue I have had for years. I never looked into it because I was lazy.

I setup my ssh keys like you probably did and I kept getting prompted for my password.

I kept entering it for years because the github docs are not that great and fail to mention how to fully setup the keys properly(maybe changed by now). I kept wondering why it was asking me to enter my password even with the ssh keys.

Well a few days ago August 13th 2021, github switched from allowing password ssh git push/pull to not allowing it and forcing you to use something like ssh keys. I went to push to my github repo and was told I had to use ssh keys blah blah…

I was like

spock WTF
Wait. WTF?

I thought I had setup the ssh-keys. But, what had happened was I had used the HTTPS method to pull the repo. So my git config was set to the HTTPS endpoint not the SSH endpoint. It had been a long time ago. I thought I had setup the keys, and I did. What I failed to do was switch the git remote value in the git config.

are you kidding me
Freaking seriously

Basically if you pulled a repo via https then you need to switch your git configuration to use the ssh url. I won’t make this article longer writing how to do that, here is a great, short article that explains it

You also need to know the ssh repo value which you can find in the repository under clone like this

github clone
remote repo name

I have not tried the newer GitHub CLI. I will eventually read about it and try it and update my repos to use it. I did write about using github ssh deploy keys here though.  And about using multiple deploy ssh keys here.

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.

Categories
Web Development

Symfony 5+ how to import or link to javascript and css files with webpack encore

Symfony has a handy way of importing your css and javascript files into your templates/pages to use them. You can find the full info in the docs link here.

This was a little confusing to me at first.  Mostly due to my lack of knowledge of some of the Webpack and  modern JS features like importing.
Basically when you update the webpack configuration to add another entry point for a separate Javascript file, it also creates a matching CSS file if your javascript file imports any CSS.  Many 3rd party packages will have imports, as I found out with SunEditor(totally awesome wysiwyg html editor). So when you try to create a script that relies on a CSS file of it’s own like suneditor you need to import like this inside the file.

//edit-about-user.js
import 'suneditor/dist/css/suneditor.min.css'
import suneditor from 'suneditor'
import plugins from 'suneditor/src/plugins'
let aboutInput = 'about-input';
suneditor.create(aboutInput, {
    plugins: plugins,
    buttonList: [
        ['undo', 'redo'],
        ['font', 'fontSize', 'formatBlock'],
        ['paragraphStyle', 'blockquote'],
        ['bold', 'underline', 'italic', 'strike', 'subscript', 'superscript'],
        ['fontColor', 'hiliteColor', 'textStyle'],
        ['removeFormat'],
        '/', // Line break
        ['outdent', 'indent'],
        ['align', 'horizontalRule', 'list', 'lineHeight'],
        ['table', 'link', 'image', 'video', 'audio' /** ,'math' */], // You must add the 'katex' library at options to use the 'math' plugin.
        /** ['imageGallery'] */ // You must add the "imageGalleryUrl".
        ['fullScreen', 'showBlocks', 'codeView'],
        ['preview', 'print'],
        ['save', 'template']
    ]
})

This is what confused me I thought the Javascript code would automatically include the CSS file when I included the Javascript in the page with a tag like this

//inside edit-about-user.html.twig
{% block javascript_extra %}
{{ encore_entry_script_tags('editAboutUser') }}
{% endblock %}

block javascipt is my custom twig section, encore_entry_script_tags includes the javascript. This is where I falsely assumed that the import statement above would include the CSS in the page along with the Javascript. The above encore_entry_script_tags function only includes the Javascript. To include the CSS which is the same from the import statement above you must use another tag.

//inside edit-about-user.html.twig
{% block stylesheets %}
{# 'app' must match the first argument to addEntry() in webpack.config.js #}
{{ parent() }}
{{ encore_entry_link_tags('editAboutUser') }}
{% endblock %}

Again block stylesheets is just my custom twig block section. encore_entry_link_tags is the magic needed to include the CSS on the page that the import statement included above. This is what confused me I thought Webpack was bundling the CSS somehow with the JS. What it does is create two files one CSS and one Javascript. You must use the encore_entry functions to include both to get what ever project you are using functioning.

So as I found out you must include both the CSS and JS with encore_entry_ tag functions.

Categories
Resources Web Development

Fonts and icons resource list

Everything you need to know about web fonts – awesome article that explains what fonts are, how they work on the web and the history of how things have changed.

Google Fonts getting started page – Use some fonts for free from google fonts. Google now has icons too.