Skip to content

Instantly share code, notes, and snippets.

@Lovelyfin00
Last active May 7, 2024 08:07
Show Gist options
  • Save Lovelyfin00/ad8b0087834365515e6787e35be35d24 to your computer and use it in GitHub Desktop.
Save Lovelyfin00/ad8b0087834365515e6787e35be35d24 to your computer and use it in GitHub Desktop.
WagtailCMS Contribution List

Thanks you so much for opening this link and reviewing my application. I'd love to showcase the contributions I made to an open-source project. The project is called WagtailCMS and it's a content management system that is built with Django. It makes creating accessible, complex and beautiful websites easy for both developers and non developers. I made over 30 PR's during the period of my outreachy Internship with them but I'll only highlight and discuss the quality ones.

Technology stacks used

  • StimulusJS: A lightweight JavaScript framework that attaches itself to the DOM through the use of HTML data attributes.
  • TypeScript
  • JavaScript
  • Jest
  • ReactJs
  • HTML/CSS
  • Storybook
  • JQuery
  • Python

Top 5 Quality Pull Requests

1. Migrated button-longrunning implemetation from legacy jQuery codes to StimulusJs

The button-longrunning is used to show that a button is loading. It works in such a way that when a button is clicked, it adds a disabled attribute to it, adds a spinner icon to show progress, and also sets a time out for when all the added attributes would be removed and the button becomes active. In some cases, it changes the current text to the data-clicked-text attribute which could be something like Loading, saving, etc. There was already a previous implemetation of this in the code but it was written with JQuery and it never gave much options for customoization like the timeout and text to be shown on the button. There were places that the previous implementation were used but not working because of unexpected bugs.

So, I created a new controller for this part of the code and converted the codes from JQuery to TypeScript. I made some modifications like using the afterLoad implemetation in Stimulus. AfterLoad is a Stimulus methods that attached the required Stimulus data-attributes to the element without you needing to add it manually. Like fot this implementation, the button-longrunning class is used in more than 20 places in the code. Imagine having to add the Stimulus data-attributes to those places and you might miss some too. But with the afterLoad, there is no need for that.

Then we have an activate method and this method is called on click of the button. Inside the method, we set the timeout for when the active attributes should display. Like the spinner and disabled attributed added to the button. There is also another Stimulus method that is being to monitor the state of a value to check if it has been changed. The valueChange callbacks allows you to monitor if a value has been changed and you can do certain things when it does. In the button-longrunning, we use it to monitor if the activate mrthod has been called, if it has, we add the active class, disabled attribute and change the button's textContent.

After I finished implementing the conversion in TypeScript, I wrote tests using Jest to test for the different behaviours. Such as applying the timeouts based on the Stimulus data-attribute provided for the timeout value, changing the text of the button and setting the disabled attributes. It was really fun working on this though I had to learn a lot of things but I enjoyed it.

2. Migrated the cleaning of the slug field to StimulusJS

Just like the slug of every website is always has those dashes and question marks that separates each words from each other, that's how it's meant to appear in WagtailCMS slug. The previous implementation was declared inside of a page-editor.js and then accessed globally. That means that whenever all the functions inside of the page-editor.js is ran everytime the file is been accessed. This slows down the performance of the website and might even cause unexpected bugs. In order to fix this, we planned on migrating all the codes from the file and move them to their own files.

The slug input field listens to an onblur event that whenever the focus is moved from the field, the value is slugified. Inside of the slugify method, there is a util called cleanForSlug that takes three parameter, the element value, urlify boolean value and the unicodeSlugsEnabled boolean value that slugifies the input. The input that the method is attached to is a under a promote_panels. So in order to attach the appropriate data-attributes, I had to create a python widget of TextInput. Inside of the widgets, I attached the appropriates data-attributes needed for Stimulus to slugify the input. Then I appended the widgets to the SlugField.

I tested for the behaviours of the method such as removing whitespeces and replacing it with dashes. And triming the extra whitespaces at the start and end of the text in the input. I also updated the python tests to emulate the latest data-attributes that has been added

3. Migrated the implementation of auto populate of the slug field with the value fron the title field to StimulusJs

The initSlugAutoPopulate() function was previously in the page-editor.js file. It's responsible for updating the value of the slug input field. The previous implementation was in JQuery so I moved it to a new TypeScript file called SyncController.ts. The SyncController is connected to the slug input field through dispatched events. it is responsible for checking if the slug input has a value, if it does, it checks if the value is the same with the title field's value. If its the same, then whenever the input of the title value is changed, it updates the slug field's vales. But if the values are not the same, it does not update it. It also checnks if the slug field's value is empty and then updates it.

The SyncController class has a static values property that defines the different configurations(data-attributes) to be applied to the controllede element, including a delay time for applying changes (delay), whether the controller is disabled (disabled), whether it operates in quiet mode (quiet), and the target element(s) for the synchronization (target).

The SyncController has different methods, including connect(), which dispatches an event to all target elements so that they can be notified that a sync has started and allows them to disable the sync by preventing default. This method is run whenever Stimulus access the DOM i.e when the DOM loads.

The check() method allows for targeted elements to determine, via preventing the default event, whether this sync controller should be disabled.

The apply() method applies a value from the controlled element to the targeted elements. It uses the setTimeout() method to introduce a delay (based on the delay option) before updating the target elements' values. If quiet is not set to true, it also dispatches a change event to each target element.

The clear() method clears the value of the targeted elements. It uses the setTimeout() method to introduce a delay (based on the delay option) before clearing the target elements' values. If quiet is not set to true, it also dispatches a change event to each target element.

The ping() method is a simple method that dispatches a ping event to the targeted elements.

Lastly we have the processTargetElements() method which returns the non-default prevented elements that are targets of this sync controller. Additionally, it allows this processing to enable or disable this controller instance's sync behavior based on the number of target elements and the number of elements that are currently prevented from the default behavior.

Then the SlugController has some modifications that allows it to get the dispatched events and access the element and value inside of it. There is the compare() method that access the dispatched event, grabs the value and param. It checks if the value gotten from the dispatched event is the same as the value of the slug input field. If the values are not the same, it prevents default else, returns the value.

The sligify method has been updated that it takes two parameter, the first is the value gotten from the dispatched event then the second is an ignoreUpdate boolean that manages whether the slug field would be updated with the title value or not. Same thing happens for the urlify method.

4. Migrate workflow and workflow tasks enable action to a Stimulus controllerJS

The ActionController class has two values defined, redirect and url. These url value is used to store the URL where the form should be submitted and a boolean value indicating whether the page should be redirected after submission.

The post method is called when the user clicks a button with a data-action attribute set to click->w-action#post. The w-action value refers to the name of the Stimulus controller, and #post refers to the name of the method to be called.

Inside the post method, a form is first created and and the form methos is set to POST while the action is set to the url value. Then an input is created which has the value WAGTAIL_CONFIG.CSRF_TOKEN and name csrfmiddlewaretoken. The input is then appended to the form. Then the redirectValue is checked to see whether the page should be redirected on submit of the form. Then the formElement is appended to the document.body. This form is then populated with the necessary data, including the CSRF token and any redirect URLs. Finally, the form is submitted. Then tests was created to check the behaviour of the button to see if it was working as expected on click.

5. Migrated Dismissible to a Stimulus w-dismissible and wrote tests for it

There is a dismissible function that is reused inside of WagtailCMS. It's attached to a button that when clicked, dismisses the parent element(container or div) its attached to. Migrating this to Stimulus was one of the cleanups planned because it is declared globally in places its been used. Inside the DismissibleController file, we have the updateDismissibles function that sends a PATCH request to the server to update the state of a dismissible element, which is an element that can be closed or dismissed by the user. The function takes an object of type Record<string, boolean> as its argument, where the keys represent the ID of the dismissible elements and the values represent the new state (i.e., whether they are dismissed or not). The function returns a Promise that resolves to a Response object. The reason why this function was not created inside the instance of the controller class is because it's been used inside of the React file where the menu dismissble component is.

DismissibleController is a class that provides the functionality to make an element dismissible. It extends the Controller class from the Stimulus library, which allows it to interact with DOM elements. The class has two static properties: classes and values. The classes property is an array that defines the CSS classes that can be used by the dismissible element, and the values property is an object that defines the default values for the dismissible element's attributes.

The class has a toggle method that is called when the dismissible element is clicked. The method updates the state of the dismissible element by setting its dismissedValue attribute to true, adding the dismissedClass to its class list, and calling the updateDismissibles function to send a PATCH request to the server with the updated state of the element.

After these I wrote test to check if it was working as expected and I also updated the python tests that were affected due to the menuItem that the dismissible is also attcahed to.

Other Major PR's I made to the project

  1. wagtail/wagtail#9882
  2. wagtail/wagtail#9818
  3. wagtail/wagtail#9801
  4. wagtail/wagtail#9828
  5. wagtail/wagtail#9768
  6. wagtail/wagtail#9463
  7. wagtail/wagtail#9383

Link to all my PRs

The link to access all my PRS can be found here

Thank you so much for taking out time to read this!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment