If you are using Tailwind 3, please see this post It’s great that Phoenix comes with Heroicons support out the box, but I use Font Awesome and wanted to use those in the same way. This post will show you how you can swap out Heroicons and make a few changes so that the .icon component works with Font Awesome instead. There are a few ways to use Font Awesome icons but this post will show you how to…
Currently, Phoenix LiveView only supports one hook per element. I recently ran into that limitation when I wanted to add functionality to a table that already had a hook on it. I came across this forum post Liveview Handling multiple phx-hook on same DOM node with a suggestion by Chris to delegate out to other hooks. Sounded simple enough, so I thought I'd give it a try. A simple delegation…
The other day I ended up enabling latency simulation in a production Phoenix LiveView. Luckily I caught it soon after deployment and was able to roll it back. NOTE: Remember that rolling it back isn’t simply to remove liveSocket.enableLatencySim(1000) , but you need to add liveSocket.disableLatencySim() to make sure it’s disabled for anyone who has already loaded the page. This led me to figure…
Phoenix LiveView is great at only updating the parts of the browser UI that have changed. Sometimes things don’t work exactly as expected and it’s nice to visually see what changes as you work on your app. This little Javascript snippet will watch for changes and highilight them for 1 second with a red outline. The script will only highlight additions to the DOM, not removals. // Mutation observer…
Destructive actions need to be handled carefully, so that a user doesn’t inadvertently lose important data. The most common solution in a web app is to provide a confirmation step before the action is applied, but providing an undo option makes deletion frictionless and safe. This pattern can also be seen in the ability to undo a send operation in email apps. While I was working on the interface…
I was working on the interface for Mailcast , where I wanted to highlight search matches and present a UI overlay for deleted items, with undo support. Each record, then, needed to know if it was selected or deleted. Mailcast interface with inline search matching and deleting items with undo support. The problem For each record, you need to know if it is selected or deleted, and in the case of the…
When a LiveView application begins to get complicated, testing it can become brittle. When we use CSS selectors to find the elements we want to assert on, we are tightly coupling our tests to the HTML structure of our LiveView. As the HTML structure changes, our tests will break. A test example from the Phoenix LiveView docs looks like this: {:ok, view, html} = live(conn, "/users") html = view |>…
The documentation for put_flash/3 in the Phoenix LiveView docs states that the @flash assign is only copied to the parent LiveView if the component calls push_navigate/2 or push_patch/2 . So if we want to set a flash message without navigation, we can use message passing to send a flash message which the parent LiveView will set on behalf of the component. A LiveComponent is run in the same…
Shorthand is the library I never “released”. I created the library six years ago and have used it in every (private) project since. But I never promoted it. This week I made some updates to make things a little easier and I thought I’d share it with the world. What is Shorthand Shorthand is an Elixir library that provides convenient syntax for working with maps and keyword lists. The library…
When creating a controller in a Phoenix application, at least three files are created: The controller (e.g. PageController), The view (e.g. PageHTML) The template (e.g. index.html.heex) This is a great structure when you’re building CRUD applications. Sometimes, especially if you have a single action, it would be nice to have a single file for the controller, view, and template. I tweeted the idea…
For a number of my projects, I purchase a .dev domain for local development. I configure DNS to point to 127.0.0.1 or similar. Most of the time I can get away with using a self-signed certificate, which I generate using the following commands: Generating a self-signed certificate This generates a wildcard certificate for *.domain.dev as well as the raw domain domain.dev . Things to change if…
HTML provides an input type datetime-local that allows the user to select a date and time. This is great for creating and editing date and time values but returns the date and time in the user’s local timezone. <.input type="datetime-local" field={@form[:datetime]} /> A component and hook to set the user’s timezone The following component creates a hidden input with a Phoenix LiveView hook that…
In a project I’m working on, I have a menu in my app layout that displays a number of unread messages. Because this is in the app layout, it does not belong to any one LiveView instance, but to all of them. When a new message comes in, or a message is marked as read, there is a broadcast on a PubSub channel that communicates the unread change. While you can subscribe to a channel within a…
This post is for Tailwind 3. There is an updated post for Tailwind 4. It’s great that Phoenix comes with Heroicons support out the box, but I use Font Awesome and wanted to use those in the same way. This post will show you how you can swap out Heroicons and make a few changes so that the .icon component works with Font Awesome instead. There are a few ways to use Font Awesome icons but this post…
This post will show you how to add a custom tooltip component to your Phoenix project that uses the PopperJS library . You can view all the code in a simple project on Github . See a diff from the initial install at 922ecca..30e278a This post wil also demonstrate how to incorporate any Javascript library that requires bindings to an element. In Phoenix that is done through Hooks. The Component In…
Elixir has some useful utility functions available in iex like h/1 which prints documentation on the given module or function/arity pair. You can add your own utility functions or macros by defining a utility module and then importing it into your .iex file. Example defmodule MyApp.IexUtilities do def u(id_or_username) do MyApp.Users.find_user(id_or_username) end end Import your utility module in…
I had a GenServer that I wanted to change the state of during a hot upgrade release, so I dutifully reached for code_change/3 as per the documentation, but no matter how hard I tried, I couldn’t get it to work. I read and re-read all the documentation I could find on releases and hot upgrades and tried and tried again but my callback was never called. I quite like Dave Thomas’ method of splitting…
Having just started using HEY , I have been forced to screen and process all my email—which is a good thing. This has helped me to unsubscribe from a lot of newsletters I was ignoring anyway. But what has been striking to me is the number of no-reply@… addresses many emails come from. I wonder why these exist? Why not send all emails from an email address that you can reply to? If I receive an…
”You keep using that word. I do not think it means what you think it means.” Words matter. Just because we’re using the same words doesn’t mean we understand each other. Semantics are important. If we agree on the meanings behind the words, then using the right words helps our understanding.
I wonder if products would be more user friendly if the CEO of the company had to demonstrate its use? When I struggle to open something or this thing doesn’t quite fit into that then it’s obvious that corners have been cut in the name of profits. This is where small businesses have the opportunity to shine. The owner is directly connected to the customer. I guess that was part of the wow factor…
This is something I need to revisit again and again. I put something on my todo list and lie to myself that it is important enough that I’ll do it today. But in the back of my mind, deep down in the recesses of my psyche, I know I’m not going to do it. I don’t really want to do it. This wastes mental energy as I revisit the need to complete the task. It also builds up a sense of guilt as undone…
Phoenix 1.4 is on it’s way and one of the big changes is that webpack is replacing brunch . If you are a SASS fan then this is how to update the default Webpack configuration to use SASS (SCSS flavour). Install NPM packages The first step is to install the node-sass and sass-loader packages from NPM. Using Yarn $ yarn add node-sass sass-loader --dev Using NPM $ npm install node-sass sass-loader…
With Phoenix 1.4 announced at ElixirConf EU ( https://youtu.be/MTT1Jl4Fs-E ) I was keen to try it out. I was specifically interested in seeing the new Webpack integration. Getting started with Phoenix 1.4 is really quite easy. Uninstall the existing Phoenix 1.3 archive From the README, Remove any previously installed phx_new archives so that Mix will pick up the local source code. This can be done…
Simply await on a promise which resolves after a timeout. test("my test", async function(assert) { // setup… await new Promise(resolve => setTimeout(resolve, 30000)); // …assert });
I have often wanted to just do the following but Ecto’s Repo module doesn’t have a count method. iex> MyApp.Repo.count(MyApp.Account) 42 It is not too difficult to create a count function that will allow you to count the results of any query. defmodule MyApp.DBUtils do import Ecto.Query, only: [from: 2] @doc "Generate a select count(id) on any query" def count(query), do: from t in…
Appending to a list in Elixir ( [1] ++ [2] ) is slower than prepending and reversing [ 2 | [1] ] |> Enum.reverse but how bad is it? Start by creating a new project, mix new benchmarking and add benchfella as a dependency in your mix.exs file defp deps do [{:benchfella, "~> 0.3.2"}] end and run mix deps.get Benchfella benchmarks work similarly to tests. Create a directory named bench and then…
TL;DR , All the code can be found here Sometimes, when you want complete control, you want to be able to install packages from source and still use an automated tool like Ansible to do that. A simple set of tasks can check for the existence of files to eliminate the need for running tasks that are already complete but that doesn’t help us with making sure we have the correct version installed. I’m…
A quick tip to make it easier to use Dead Man's Snitch with the whenever gem Whenever is a great gem for managing cron jobs. Dead Man’s Snitch is a fantastic and useful tool for making sure those cron jobs actually run when they should. Whenever includes a number of predefined job types which can be overridden to include snitch support. The job_type command allows you to register a job type. It…
I’ve found a number of times where I have needed to iterate over a hash and modify the values. The most recent was stripping excess spaces from the values of a Rails params hash. The only way I know of doing this is: hash = {one: " one ", two: "two "} hash.each do |key, value| hash[key] = value.strip! end #=> {:one=>“one”, :two=>“two”} This is a lot less elegant than using map on an Array [" one…
I quickly drew out the graph from the video on determining great feature fit. What you're looking for is features that will be used by all your users all of the time.
I use a large 27" iMac which I divide up windows with a browser in the top right of the screen. One thing that often frustrated me is that I could not maximise a video to fill the window completely. I had to fill my entire screen or watch it in the embedded size. It turns out this is not too hard, change the URL in the browser from https://www.youtube.com/ watch?v= oHg5SJYRHA0 to…
I recently had an import job failing because it took too long. When I had a look at the file I saw that there were 74 useful lines but a total of 1,044,618 lines in the file (My guess is MS Excel having a little fun with us). Most of the lines were simply rows of commas: Row,Of,Headers some,valid,data ,, ,, ,, ,, ,, The CSV library has an option named skip_blanks but the documentation says “Note…
I won’t cover all the boiler plate code but you can view that at JSFiddle The project is a ListItem model and a corresponding ListCollection . There is a ListItemView which is compiled into a ListView to create an ordered list. There is a FormView used for adding items to the collection. The first component of our code is the comparator in the collection which keeps the list sorted by name. var…
If you want to view the SQL query used to construct the information returned from a psql command (which will help you learn the underlying information schema) then type \set ECHO_HIDDEN $ psql test psql (9.4.1) Type "help" for help. test=# \set ECHO_HIDDEN test=# \dt ********* QUERY ********** SELECT n.nspname as "Schema", c.relname as "Name", CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN…
I recently had a requirement where I needed an account to have zero, one or two actions associated with it. One could be a single action and the other could be one of many repeating types. I didn’t want two single actions and I didn’t want two or more types of repeating actions. To solve this I used two partial indexes to split the data set and apply a unique constraint to each set. CREATE TABLE…
An overview of how Fibers work in Ruby Fibers are code blocks that can be paused and resumed. They are unlike threads because they never run concurrently. The programmer is in complete control of when a fiber is run. Because of this we can create two fibers and pass control between them. Control is passed to a fiber when you call Fiber#resume , the Fiber returns control by calling Fiber.yield…
I’m working on an app that creates user accounts and (optionally) subscribes users to our mailing list. Because I’m handling user creation in my app, I need some way to add them to the mailing list which is hosted on MailChimp . To do this, I am using their API to send through subscriber information. The documentation for the ruby gem is not great. You have a few choices: The MailChimp API Docs…
I got a message from a client this morning telling me that all users could see all reports on our product. Not good. I use CanCan to manage permissions and until now it has served me well. What went wrong? Whether a bug or not, I discovered that a very recent change I made had openned up the hole. I wanted to have a permission setting that could prevent anyone from seeing any reports as well as…
I've just been working on a project where a number of downloads needed to be restricted to specific users. I needed to authenticate the user and then allow them access to the file. This is not too difficult in rails: def download if authenticated? send_file #{RAILS_ROOT}/downloads/images/myfile.zip' end end The problem with this is that if the file is large, rails will spend a lot of time sending…