User: Admin: Difference between revisions
No edit summary |
No edit summary |
||
| (34 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
<source> | |||
from(a in Article, | |||
order_by: [desc: a.inserted_at], # Assuming 'inserted_at' is the timestamp for when the article was created or updated | |||
limit: 8 | |||
) | |||
|> Repo.all() | |||
</source> | |||
<source> | |||
defmodule AppWeb.Landing do | |||
use AppWeb, :live_view | |||
alias App.Testbeds | |||
alias App.Testbeds.Testbed | |||
alias App.Repo # Ensure to alias Repo | |||
import Ecto.Query # Import Ecto.Query to work with queries | |||
def mount(_params, _session, socket) do | |||
# Default to ordering by name | |||
testbeds = Repo.all(from(t in Testbed, order_by: t.name)) | |||
# Assign the testbeds to the socket for rendering | |||
{:ok, assign(socket, testbeds: testbeds, sort: :name)} | |||
end | |||
def render(assigns) do | |||
~H""" | |||
<h1>Hello World! YAY</h1> | |||
<div> | |||
<!-- Buttons to toggle sorting --> | |||
<button phx-click="sort_by_name">Sort by Name</button> | |||
<button phx-click="sort_by_updated_at">Sort by Updated By</button> | |||
</div> | |||
<ul> | |||
<%= for testbed <- @testbeds do %> | |||
<li><%= testbed.name %></li> | |||
<% end %> | |||
</ul> | |||
""" | |||
end | |||
def handle_event("sort_by_name", _params, socket) do | |||
testbeds = Repo.all(from(t in Testbed, order_by: t.name)) | |||
{:noreply, assign(socket, testbeds: testbeds, sort: :name)} | |||
end | |||
def handle_event("sort_by_updated_at", _params, socket) do | |||
testbeds = Repo.all(from(t in Testbed, order_by: t.inserted_at)) | |||
{:noreply, assign(socket, testbeds: testbeds, sort: :inserted_at)} | |||
end | |||
| Line 20: | Line 59: | ||
</source> | </source> | ||
_______________________________________________________ | |||
* Create a form with a post request. | |||
* Create the route needed for that request | |||
* Create the controller needed for that route | |||
==Form== | |||
<pre> | |||
<.form action={~p"/todos"} method="post" class="mt-10 space-y-8"> | |||
<div> | |||
<label for="contact_name" class="block text-sm font-semibold leading-6 text-zinc-800"> | |||
Name | |||
</label> | |||
<input | |||
id="todo" | |||
type="text" | |||
name="todo" | |||
class="mt-2 block w-full rounded-lg text-zinc-900 focus:ring-0 sm:text-sm sm:leading-6 border-zinc-300 focus:border-zinc-400" | |||
/> | |||
</div> | |||
<button | |||
type="submit" | |||
class="rounded-lg bg-zinc-900 hover:bg-zinc-700 py-2 px-3 text-sm font-semibold leading-6 text-white active:text-white/80" | |||
> | |||
Save Contact | |||
</button> | |||
</.form> | |||
</pre> | |||
==Controller== | |||
<pre> | |||
# In your TodoController | |||
defmodule AppWeb.TodoController do | |||
use AppWeb, :controller | |||
# Action to handle the form submission (POST request) | |||
def create(conn, %{"todo" => todo_name}) do | |||
# Here you can handle the form data (e.g., save it to the database) | |||
IO.puts("Received Todo: #{todo_name}") | |||
# For now, let's just redirect to the home page | |||
conn | |||
|> put_flash(:info, "Todo saved!") | |||
|> redirect(to: "/") | |||
end | |||
end | end | ||
</pre> | |||
________________________________________________________ | |||
# CRUD Forms | |||
To understand CRUD forms you will need to understand the following concepts. | |||
* Ecto Schema | |||
* Ecto Context | |||
* Form Basics | |||
* form/1 | |||
* simple_form/1 | |||
* changesets | |||
We are not going to go in order. | |||
The Context provides CRUD functions for the chosen data schema. Phoenix contexts define the interface through which other parts of the codebase can interact with the app layer. | |||
_______________________ | |||
* Create routes | |||
* create page with form (submit get request) | |||
(ecto schema, context, changesets) | |||
def | _______________________________________________________ | ||
In that case, you really should switch it to prod mode. Instead of doing all that above with the "config/secret.exs" file, create the ".env" in the project root, then add DotenvParser.load_file(".env") to "config/runtime.exs" as the first line inside the if config_env() == :prod do. In the .env file, set the DATABASE_URL and SECRET_KEY_BASE. Then run it as MIX_ENV=prod mix phx.server. | |||
<source> | |||
def get_titles do | |||
urls = [ | |||
"https://cointelegraph.com/rss", | |||
"https://chaski.huffpost.com/us/auto/vertical/us-news" | |||
# Add more RSS feed URLs here | |||
] | |||
urls | |||
|> Enum.map(fn url -> | |||
with {:ok, feed} <- ElixirRss.fetch_and_parse(url) do | |||
Enum.map(feed.entries, & &1.title) | |||
else | |||
{:error, reason} -> {:error, reason} | |||
end | |||
end) | |||
end | end | ||
</source> | </source> | ||
<source> | <source> | ||
defmodule App do | |||
def get_titles() do | |||
data = [ | |||
"https://cointelegraph.com/rss", | |||
"https://chaski.huffpost.com/us/auto/vertical/us-news" | |||
# Add more RSS feed URLs here | |||
] | |||
data | |||
|> Enum.map(fn url -> | |||
case ElixirRss.fetch_and_parse(url) do | |||
{:ok, feed} -> feed.entries |> Enum.map(fn entry -> entry end) | |||
{:error, reason} -> {:error, reason} | |||
end | |||
end) | |||
|> Enum.map(fn entry_list -> | |||
entry_list | |||
|> Enum.map(fn entry -> entry.title end) | |||
end) | |||
end | end | ||
def hello do | |||
def | rss_feeds = [ | ||
"https://cointelegraph.com/rss", | |||
"https://chaski.huffpost.com/us/auto/vertical/us-news" | |||
# Add more RSS feed URLs here | |||
] | |||
rss_feeds | |||
|> Enum.map(fn url -> | |||
case ElixirRss.fetch_and_parse(url) do | |||
{:ok, feed} -> feed.entries |> Enum.map(fn entry -> entry end) | |||
{:error, reason} -> {:error, reason} | |||
end | |||
end) | |||
end | end | ||
end | end | ||
</source> | </source> | ||
<source> | |||
defmodule AppWeb.PageLive do | |||
use AppWeb, :live_view | |||
def mount(_params, _session, socket) do | |||
if connected?(socket) do | |||
Phoenix.PubSub.subscribe(App.PubSub, "message") | |||
end | |||
{:ok, assign(socket, message_item: "different")} | |||
end | |||
def handle_info({:pubsub_transmission, text}, socket) do | |||
IO.inspect "pub sub working" | |||
{:noreply, assign(socket, message_item: text)} | |||
end | |||
def loop(data) do | |||
IO.inspect data | |||
if data > 0 do | |||
IO.inspect "notify" | |||
# run code to compare files, loop. If different run pubsub code below | |||
:timer.sleep(2000) | |||
loop(data-1) | |||
end | |||
if data <= 0 do | |||
Phoenix.PubSub.broadcast(App.PubSub, "message", {:pubsub_transmission, "data"}) | |||
"done" | |||
end | |||
end | |||
def handle_event("change-text", _params, socket) do | |||
spawn(fn -> loop(3) end) | |||
{:noreply, assign(socket, message_item: "START" )} | |||
end | |||
def render(assigns) do | |||
# AppWeb.PageLive.loop("xxx") | |||
~H""" | |||
SANDBOX | |||
<.button phx-click="change-text" value={@message_item}>CLICK ME</.button> | |||
<div> | |||
<%= @message_item %> | |||
</div> | |||
""" | |||
end | |||
end | |||
</source> | |||
elixir : 1.16.2 | |||
Erl: 20 | |||
# PostGres | |||
Created Saturday 18 February 2023 | |||
Launch [PostGres](#PostGres) from Terminal | |||
------------------------------------------ | |||
sudo -u postgres psql | |||
</ | Set User Permission | ||
------------------- | |||
<https://commandprompt.com/education/how-to-create-a-superuser-in-postgresql/> | |||
ALTER USER user_name WITH PASSWORD 'new_password'; | |||
Run Post Gres Commands | |||
---------------------- | |||
sudo -i -u postgres | |||
psql | |||
Create and Delete Database | |||
CREATE DATABASE name; | |||
DROP DATABASE name; | |||
## View user list | |||
\du | |||
View all databases | |||
postgres=# \l | |||
Change databases | |||
postgres=# \c name-of-database | |||
View Tables | |||
After you change databases and are selected on one of them do this: | |||
\dt | |||
SELECT * FROM mytablename; | |||
EMPTY TABLE | |||
DELETE FROM name-of-table | |||
CREATE TABLE | |||
CREATE TABLE table_name( | |||
column1 datatype, | |||
column2 datatype, | |||
column3 datatype, | |||
..... | |||
columnN datatype, | |||
PRIMARY KEY( one or more columns ) | |||
); | |||
INSERT | |||
INSERT INTO ITEMS(id, name) VALUES(1,'htvjbjbgni'); Strings are single quote | |||
==Roadmap== | |||
<source> | |||
defmodule AppWeb.ItemsLive do | |||
use AppWeb, :live_view | |||
def mount(_params, _session, socket) do | |||
{:ok, assign(socket, switches: ["grindy","sonic","ogre"])} | |||
end | |||
def handle_event("run",_params,socket) do | |||
IO.inspect "DOWNLOAD VIA SSH" | |||
{:noreply, redirect(socket, to: "/download")} | |||
end | |||
end | |||
def render(assigns) do | |||
~H""" | |||
= | <ul> | ||
=== | <%= for switch <- @switches do %> | ||
<a phx-submit="click" href="/download"> <li> <%= switch %> </li> </a> | |||
< | <%end%> | ||
</ul> | |||
""" | |||
end | |||
end | |||
</source> | |||
==Notes== | |||
List of Switches and the option to select the one needed. | |||
Button to download JSON file. | |||
Drag and Drop Palette to upload JSON file. | |||
After JSON file is uploaded a button titled "Make Active" is available. | |||
https://medium.com/@fraiha26/pushing-events-to-livecomponents-from-js-hooks-w-phoenix-elixir-d5544d4c3dfa | |||
Changeset notes and forms | |||
NOTE: Use case statements for pattern matching | |||
____________________________________________ | |||
Create a basic <.form></form> without error handling | |||
<source> | <source> | ||
<.form :let={f} for={@changeset} action={~p"/items"} > | |||
<.input field={f[:title]} type="text" label="Title" /> | |||
<.button>Save Item</.button> | |||
</.form> | |||
</source> | </source> | ||
With error handling looks like this: | |||
<source> | <source> | ||
<.form :let={f} for={@changeset} action={~p"/items"} > | |||
<.error :if={@changeset.action}> | |||
Oops, something went wrong! Please check the errors below. | |||
</.error> | |||
<.input field={f[:title]} type="text" label="Title" /> | |||
<.button>Save Item</.button> | |||
</.form> | |||
</source> | |||
Create the routes for render and post to endpoint. | |||
The controller for the page render needs to pass in the changeset via ```Items.change_item(%Item{})``` | |||
<source> | <source> | ||
def | def new(conn, _params) do | ||
changeset = Items.change_item(%Item{}) | |||
render(conn, :new, changeset: changeset) | |||
end | end | ||
</source> | </source> | ||
Post route without error handling | |||
<source> | <source> | ||
def create(conn, %{"item" => item_params}) do | |||
case Items.create_item(item_params) do | |||
{:ok, item} -> | |||
conn | |||
|> put_flash(:info, "Item created successfully.") | |||
|> redirect(to: ~p"/items/#{item}") | |||
end | |||
end | |||
</source> | |||
With error handling | |||
<source> | |||
def create(conn, %{"item" => item_params}) do | |||
case Items.create_item(item_params) do | |||
{:ok, item} -> | |||
conn | |||
|> put_flash(:info, "Item created successfully.") | |||
|> redirect(to: ~p"/items/#{item}") | |||
{:error, %Ecto.Changeset{} = changeset} -> | |||
render(conn, :new, changeset: changeset) | |||
end | |||
end | |||
</source> | </source> | ||
_____________________________________________ | |||
https://medium.com/@vincentlin/phoenix-very-simple-form-c4b121697fcb | |||
'''home.html.heex''' | |||
<source> | <source> | ||
<form action="/submit" method="post"> | |||
<label for="name">Name:</label><br> | |||
<input type="text" id="name" name="name"><br> | |||
<input type="hidden" name="_csrf_token" value={@csrf_token} /> | |||
<input type="submit" value="Submit"> | |||
</form> | |||
</source> | </source> | ||
page_controller.ex | |||
<source> | <source> | ||
defmodule AppWeb.PageController do | |||
use AppWeb, :controller | |||
def home(conn, _params) do | |||
# The home page is often custom made, | |||
csrf_token = Plug.CSRFProtection.get_csrf_token() | |||
render(conn, :home, csrf_token: csrf_token) | |||
end | end | ||
def submit(conn, _params) do | |||
# The home page is often custom made, | |||
IO.inspect(_params) | |||
IO.inspect "___________________________" | |||
csrf_token = Plug.CSRFProtection.get_csrf_token() | |||
render(conn, :home, csrf_token: csrf_token) | |||
end | |||
end | |||
</source> | |||
router.ex | |||
<source> | |||
scope "/", AppWeb do | |||
pipe_through :browser | |||
post "/submit", PageController, :submit | |||
get "/", PageController, :home | |||
end | end | ||
</source> | |||
Ecto.Adapters.SQL.query(Repo, "select * from artists where id=1") | |||
Ecto cheatsheet: https://hexdocs.pm/ecto/crud.html | |||
__________________________________________________________________ | |||
Most programming languages give you the ability to set global state by creating globally scoped variables. Elixir does not do this. Instead, Elixir provides a tool called a "processes" to persist state. Elixir applications are composed of these "processes". | |||
Processes by themselves are complicated to use, thus Elixir provides abstractions to make working with processes easier. One of these abstractions is called a GenServer. | |||
GenServers are not only used to store state, they are also used to invoke work independent of the main application process. | |||
==Creating a GenServer to Store State== | |||
To Create a GenServer, start by creating a basic Module that ''uses'' the GenServer module (image below) | |||
<source> | |||
defmodule App do | |||
use GenServer | |||
end | |||
</source> | </source> | ||
We now need to describe the actions we want to perform. Being that this is used to store state, we will use basic CRUD actions: Create, Read, Update, Delete. To begins, empty functions describing each action is created. | |||
<source> | <source> | ||
defmodule App | defmodule App do | ||
def create do | |||
end | |||
def read do | |||
end | |||
def update do | |||
end | |||
def delete do | |||
end | |||
end | end | ||
| Line 681: | Line 634: | ||
</source> | </source> | ||
==What now?== | |||
To fill in the functions with the appropriate code we must first have a basic understanding of how GenServers are structured. | |||
GenServers are composed of the GenServer module invoking callbacks. The callbacks have built in names that you, as a developer have to use. | |||
These callbacks are named: | |||
* init | |||
* handle_cast | |||
* handle_call | |||
To demonstrate, here is an example : | |||
<source> | |||
{:ok, pid} = GenServer.start(ModuleNameOfGenServer, %{}) | |||
</source> | |||
The above function named "start" calls the function named init in the Module below. | |||
In GenServer syntax the name of the GenServer functions always invoke callbacks that are determined by the language. | |||
<source> | |||
defmodule ModuleNameOfGenServer do | |||
# 1 | |||
def init(state \\ %{}) do | |||
{:ok, state} | |||
end | |||
</source> | |||
This is a list of the GenServer functions and their corresponding callbacks. | |||
* GenServer.start/1 --> init/1 | |||
* GenServer.call/3 --> def handle_call/3 | |||
* GenServer.cast/2 --> handle_cast/2 | |||
== Start and Call == | |||
Call and Cast can be used to perform the same operations. The | |||
__________________________________________________________ | |||
Genserver timed events | |||
<source> | <source> | ||
defmodule Go do | |||
use GenServer | |||
def go() do | |||
GenServer.start_link(__MODULE__, []) | |||
def | |||
end | end | ||
def init(list) do | |||
" | :timer.apply_interval(1000, __MODULE__, :add, [self(), "weeeee"]) | ||
{:ok, list} | |||
end | end | ||
def | def view(pid) do | ||
GenServer.call(pid, :view) | |||
end | |||
def handle_call(:view, _from, list) do | |||
{:reply, list, list} | |||
end | |||
def add(pid, new_item) do | |||
GenServer.call(pid, {:item, new_item}) | |||
end | |||
def handle_call({:item, new_item}, _from, state) do | |||
result = [new_item] ++ state | |||
{:reply, result, result} | |||
end | end | ||
end | end | ||
</source> | |||
<source> | <source> | ||
defmodule ShoppingList do | |||
use GenServer | |||
def start_link() do | |||
GenServer.start_link(ShoppingList, []) | |||
end | |||
def init(list) do | |||
{:ok, list} | |||
end | |||
def view(pid) do | |||
GenServer.call(pid, :view) | |||
end | |||
def handle_call(:view, _from, list) do | |||
{:reply, list, list} | |||
end | |||
def add(pid, new_item) do | |||
GenServer.call(pid, {:item, new_item}) | |||
end | |||
def handle_call({:item, new_item}, _from, state) do | |||
result = [new_item] ++ state | |||
{:reply, result, result} | |||
end | |||
end | |||
</source> | </source> | ||
https://blog.appsignal.com/2018/06/12/elixir-alchemy-deconstructing-genservers.html | |||
<source> | |||
defmodule KeyValue do | |||
# 1 | |||
def init(state \\ %{}) do | |||
{:ok, state} | |||
end | |||
# 2 | |||
def handle_cast({:put, key, value}, state) do | |||
{:noreply, Map.put(state, key, value)} | |||
end | |||
# 3 | |||
def handle_call({:get, key}, _from, state) do | |||
{:reply, Map.fetch!(state, key), state} | |||
end | |||
end | |||
# {:ok, pid} = GenServer.start(KeyValue, %{}) | |||
# GenServer.cast(pid, {:put, :foo, "bar"}) | |||
# GenServer.call(pid, {:get, :foo}) | |||
</source> | |||
Send messages via these functions wrapped in module functions .You can look at these are functions used to fire "GenServer Events". | |||
* start_link | |||
* GenServer.call | |||
* GenServer.cast | |||
The corresponding server functions that reply are: | |||
* init | |||
* handle__call | |||
* handle_cast | |||
* | |||
These can be looked at as "listeners" - in JavaScript parlance. | |||
__________________________________________ | |||
If you have experience with programming languages other than Elixir the idea of GenServers may be very foreign and require a strong context shift to learn. This document is intended to simplify the concept. | |||
* What: GenServers are simply a way to hold state and/or invoke actions based on incoming events. | |||
* when | |||
* where | |||
* why | |||
* how | |||
___________________________________________ | |||
GenServer timer example | |||
<source> | <source> | ||
defmodule | defmodule Timer do | ||
use GenServer | |||
def init(_) do | |||
:timer.send_interval(1000, :xyz) | |||
{:ok, 0} | |||
end | end | ||
def handle_info(:xyz, state) do | |||
IO.inspect(state) | |||
{:noreply, state + 1} | |||
end | end | ||
end | |||
</source> | |||
<source> | |||
[ | |||
{ | |||
data:{ | |||
creationTime, | |||
publishDate, | |||
} | |||
featured:{ | |||
setAmount, | |||
items:[] | |||
}, | |||
children:{ | |||
setAmount, | |||
items:[] | |||
}, | |||
links:{ | |||
setAmount, | |||
items:[] | |||
} | |||
} | |||
] | |||
</source> | |||
jason.encode / jason.decode | |||
<source> | |||
def run(num) do | |||
Enum.each(0..99999, fn(_x) -> | |||
task = Task.async(fn -> num + _x end) | |||
IO.inspect Task.await(task) | |||
end) | |||
</source> | |||
Enum.map(feed.entries, fn x -> %{url: x.url, title: x.title} end) | |||
App is split: | |||
App that captures RSS in UI and lets admin select stories. THere are settings to determine the number of FEATURED,CHILD and LINK stories. | |||
News categories are horizontal and scrollable. | |||
This exports a json doc with all RSS data for each story. | |||
The | |||
App that reads the json data. This app creates tables and is the front end. | |||
https://stephenbussey.com/tags/elixir.html | |||
<pre> | |||
defmodule AppWeb.GroupLive do | |||
use AppWeb, :live_view | |||
alias App.TestBeds | |||
alias App.Groups | |||
def mount(_params, _session, socket) do | |||
{:ok, assign(socket, testbeds: TestBeds.list_testbeds(), groups: Groups.list_groups())} | |||
end | |||
def handle_event("select-group", params, socket) do | |||
group = App.Groups.get_group!(params["group-id"]) | |||
testbed = App.TestBeds.get_test_bed!(params["testbed_id"]) | |||
App.TestBeds.update_test_bed(testbed, %{group_id: group.id}) | |||
{:noreply, socket} | |||
end | |||
def updated_groups(testbed_id) do | |||
testbed = App.TestBeds.get_test_bed!(testbed_id) | |||
groups = App.Groups.list_groups | |||
tail = Enum.filter(groups, fn(item)-> item.id !== testbed.group_id end) | |||
head = Enum.filter(groups, fn(item)-> item.id == testbed.group_id end) | |||
|>Enum.at(0) | |||
[head | tail] | |||
end | |||
def render(assigns) do | |||
~H""" | |||
<%= for testbed <- Enum.sort_by(@testbeds , &("#{&1.name}"), :asc)do %> | |||
<div class="grid grid-cols-4 gap-4 p-8 rounded shadow bg-white"> | |||
<h2> Name</h2> | |||
<div class="name"><%= testbed.name %> </div> | |||
<h2> Group</h2> | |||
<div> | |||
<form phx-change="select-group" name="testbed_id" > | |||
<input type="hidden" name="testbed_id" value={testbed.id}/> | |||
<select id="group-selection" name="group-id" class=""> | |||
<%= for group <- updated_groups(testbed.id) do %> | |||
<%= if group !== nil do %> | |||
<option value= {"#{group.id}"}><%=group.name %></option> | |||
<%else%> | |||
<option value= {"Unassigned"}>NONE</option> | |||
<% end %> | |||
/ | <% end %> | ||
</select> | |||
</form> | |||
</div> | |||
</div> | |||
<% end %> | |||
""" | |||
end | |||
end | |||
</pre> | |||
</ | |||
== Developer Notes == | |||
===Groups Feature === | |||
To complete this feature do these things. | |||
* Run through the steps in [http://elixirblocks.com/index.php?title=Create_Foreign_Key_Relationship_Between_Ecto_Tables | this document] to create Group table. | |||
* Groups have these fields '''name: string''', '''description: string''' and '''color: string''' | |||
* Add unique constraint in migration file: | |||
<pre> | |||
def change do | |||
create unique_index(:groups, [:name]) | |||
end | |||
</pre> | |||
* Port over Group LiveView code (temporarily placed at discussion page). | |||
* On completion '''possibly''', create a Group named '''No Group''' and set all Testbeds to it. | |||
________ | |||
https://blog.appsignal.com/2022/10/11/phoenix-liveview-018-new-special-html-attributes.html | |||
https://blixtdev.com/whats-new-phoenix-1-7/ | |||
https://github.com/devato/inertia_phoenix | |||
https://phoenixonrails.com/blog/you-can-stop-using-form-for-in-phoenix | |||
https://samuelmullen.com/articles/phoenix_templates_rendering_and_layouts | |||
WorkFlow of Todo App in Both LiveView and DeadView. | |||
Note: | |||
Create foreign key data. | |||
Create live view. List all testbeds and form to assign each a group by group-name. | |||
Repeat in dead view | |||
Mental model and data flows | |||
home.html.heex | |||
<a href="/data" name="we are the world" > click me</a> | |||
<%= @message %> | |||
page controller | |||
<pre> | |||
defmodule AppWeb.PageController do | |||
use AppWeb, :controller | |||
def | def home(conn, _params) do | ||
IO.inspect _params | |||
render(conn, :home, message: "Hello") | |||
end | end | ||
end | |||
</pre> | |||
get "/:yay", PageController, :index | |||
get "/", PageController, :index | |||
* Explain control flow of "Dead Views" and the Generator HTML Control flow. | |||
* Proper Way to use Modal | |||
* How to give Focus to Fields in Modal (or other forms fields) | |||
* | |||
* | |||
* How to Create Database Seed Data. | |||
* How to structure code so controller code is in one file and HTML Heex is in another | |||
* How to Use Generators in a Real World Project | |||
* How to Work with Database Data via Basic Commands and Custom Change sets. | |||
* How to Render Database Data to LiveView (via Context Commands). | |||
* How to Create a Chat using PubSub. | |||
* How to Create Event Handlers in LiveView | |||
* How to Create a Dynamic Route from Scratch. | |||
* How to Use Elixirs Template language | |||
* How to Work with Modal | |||
* How to Use CSS From Scratch (Without bundler) | |||
* How to use JS hooks | |||
______________________ | |||
https://studioindie.co/blog/heex-guide/ | |||
<source> | |||
get "/", PageController, :some-atom #This is the route. some-atom is the controller name | |||
</source> | </source> | ||
<source> | <source> | ||
defmodule AppWeb. | defmodule AppWeb.PageController do | ||
use AppWeb, : | use AppWeb, :controller | ||
def some-atom(conn, _params) do # controller name | |||
# The home page is often custom made, | |||
# so skip the default app layout. | |||
render(conn, :some-atom, layout: false) # :some-atom is template name | |||
end | |||
end | |||
</source> | |||
Controllers page_html.ex contains | |||
<source> | |||
defmodule AppWeb.PageHTML do | |||
use AppWeb, :html | |||
embed_templates "page_html/*" | |||
end | |||
</source> | |||
'''page_html''' is the name of the folder in '''Controllers/page_html''' | |||
Controllers/page_html/some-atom.html.heex (this is the template) | |||
__________________________________________ | |||
Set all association with preload: | |||
<source> | |||
def list_groups do | |||
Repo.all(Group) | |||
|> Repo.preload([:testbeds]) | |||
end | |||
</source> | |||
=== Insert Child into Parent === | |||
Example: | |||
<source> | |||
App.get_group!(2) # arg is id | |||
|> Ecto.build_assoc(:testbeds) | |||
|> Repo.insert() | |||
Or | |||
group = App.get_group!(2) # arg is id | |||
testbeds = Ecto.build_assoc(group, :testbeds) | |||
App.Group.create_group(testbeds) | |||
</source> | |||
<source> | |||
group = App.Groups.get_group!(1) | |||
thing = Ecto.build_assoc(group, :testbeds, name: "DUMB") | |||
alias App.{Repo} | |||
Repo.insert(thing) | |||
</source> | </source> | ||
| Line 1,180: | Line 1,112: | ||
==Example== | |||
<source> | |||
avatar = %Avatar{nick_name: "Elixir", pic_url: "http://elixir-lang.org/images/logo.png"} | |||
user = %User{name: "John Doe", email: "john.doe@example.com", avatar: avatar} | |||
</source> | |||
https://www.reddit.com/r/elixir/comments/oh318k/is_there_a_way_to_avoid_ectoassociationnotloaded/h4mmcfi/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button | |||
# Create Group Table | |||
<source> | <source> | ||
mix phx.gen.html Groups Group groups name:string | |||
</source> | |||
Set the resources / | |||
<source> | |||
mix ecto.migrate | |||
</source> | |||
def | For testbeds create a migration file and add this field: group_id:references:groups | ||
<source> | |||
def change do | |||
alter table(:testbeds) do # customize to your code | |||
add :group_id, references(:groups) | |||
end | |||
end | |||
</source> | |||
Update Groups Schema (Don't forget alias / has_many) | |||
<source> | |||
<form phx- | defmodule App.Groups.Group do | ||
use Ecto.Schema | |||
import Ecto.Changeset | |||
alias App.Testbeds.Testbed # Alias! | |||
schema "posts" do | |||
field :body, :string | |||
field :title, :string | |||
has_many :testbeds, Testbed # Has many | |||
timestamps() | |||
end | |||
@doc false | |||
def changeset(post, attrs) do | |||
post | |||
|> cast(attrs, [:title, :body]) | |||
|> validate_required([:title, :body]) | |||
end | |||
end | |||
</source> | |||
TESTBEDS Schema | |||
<source> | |||
defmodule App.Testbeds.Testbed do | |||
use Ecto.Schema | |||
import Ecto.Changeset | |||
alias App.Groups.Group # Alias | |||
schema "testbeds" do | |||
field :name, :string | |||
belongs_to :group, Group # Belongs to | |||
timestamps() | |||
end | |||
@doc false | |||
def changeset(testbed, attrs) do | |||
testbed | |||
|> cast(attrs, [:name]) | |||
|> validate_required([:name]) | |||
end | |||
end | |||
</source> | |||
_____________________________________ | |||
Side notes. | |||
defmodule App.Repo.Migrations.CreateTestbeds do | |||
use Ecto.Migration | |||
def change do | |||
create table(:testbeds) do | |||
add :name, :string | |||
add :group_id, references(:groups, on_delete: :nothing) | |||
timestamps() | |||
end | |||
create index(:testbeds, [:group_id]) | |||
end | |||
end | |||
________________________________ | |||
mix phx.gen.context Comments Comment comments name:string content:text post_id:references:posts | |||
mix phx.gen.html Testbeds Testbed testbeds name:string group_id:references:groups | |||
https://graffino.com/web-development/first-steps-in-elixir-and-phoenix-create-a-blog-prototype | |||
https://blog.logrocket.com/getting-started-ecto-phoenix/ | |||
https://serokell.io/blog/ecto-guide-for-beginners | |||
mix phx.gen.html Testbeds Testbed testbeds name:string | |||
mix phx.gen.html Groups Group groups name:string | |||
Create migration for | |||
mix ecto.gen.migration add_testbed_group_reference | |||
Migration: | |||
<source> | |||
def change do | |||
alter table(:testbeds) do # customize to your code | |||
add :group_id, references(:groups) | |||
end | |||
end | |||
</source> | |||
Manually Change: | |||
<source> | |||
defmodule App.Group do | |||
use Ecto.Schema | |||
schema "groups" do | |||
field :title, :string | |||
field :tagline, :string | |||
has_many :testbeds, App.Testbed | |||
end | |||
end | |||
</source> | |||
testbeds | |||
<source> | |||
defmodule App.Repo.Migrations.AddTextbedGroups do | |||
use Ecto.Migration | |||
def change do | |||
create table(:testbeds) do | |||
add :name, :string | |||
add :group_id, references(:groups) | |||
end | |||
end | |||
end | |||
</source> | |||
<source> | |||
# lib/app/testbeds.ex | |||
defmodule App.Testbed do | |||
use Ecto.Schema | |||
schema "testbedss" do | |||
field :name, :string | |||
belongs_to :group, App.Group | |||
end | |||
end | |||
</source> | |||
_____________________ | |||
%App.TestBeds.TestBed{ | |||
__meta__: #Ecto.Schema.Metadata<:loaded, "testbeds">, | |||
id: 1, | |||
developer: "None", | |||
name: "sdfsd", | |||
owner: "sdfsddf", | |||
note: "sdsfsddf", | |||
status: "Available", | |||
url: "sdf", | |||
version: "ddf", | |||
manager: "sf", | |||
inserted_at: ~N[2023-08-21 13:45:39], | |||
updated_at: ~N[2023-08-21 13:45:44] | |||
} | |||
________________________________________________________________--- | |||
<source> | |||
defmodule AppWeb.SendLive do | |||
use AppWeb, :live_view | |||
def mount(_params, _session, socket) do | |||
{:ok, socket} | |||
end | |||
def handle_event("send", %{"text" => text}, socket) do | |||
AppWeb.Endpoint.broadcast(topic, "message", text) | |||
{:noreply, socket} | |||
end | |||
defp topic do | |||
"chat" | |||
end | |||
def render(assigns)do | |||
~H""" | |||
<div> | |||
<h1>Send Message</h1> | |||
<form phx-submit="send"> | |||
<input type="text" name="text" /> | |||
<button type="submit">Send</button> | |||
</form> | |||
</div> | |||
""" | |||
end | |||
end | |||
</source> | |||
______________________________________________________________ | |||
<source> | |||
defmodule AppWeb.ReceiveLive do | |||
use AppWeb, :live_view | |||
def mount(_params, _session, socket) do | |||
if connected?(socket) do | |||
AppWeb.Endpoint.subscribe(topic) | |||
end | |||
{:ok, assign(socket, messages: "")} | |||
end | |||
def handle_info(%{event: "message", payload: message}, socket) do | |||
IO.inspect message | |||
{:noreply, assign(socket, messages: message)} | |||
end | |||
defp topic do | |||
"chat" | |||
end | |||
def render(assigns)do | |||
~H""" | |||
<div> | |||
<h1>ChatLive</h1> | |||
<%= @messages %> | |||
</div> | |||
""" | |||
end | |||
end | |||
</source> | |||
______________________________________________________________ | |||
When you hover over a list of items this hook finds items with the same name and sets the opacity of all others to 0. You see all the items similar to the one you are hovering over. | |||
<source> | |||
TestbedVersionHover: { | |||
// https://jsfiddle.net/IrvinDominin/7K2Z3/ | |||
mounted() { | |||
let rows = document.getElementsByClassName("testbed-version") | |||
// this.ele = element | |||
// rows = this.ele; | |||
for (var i = 0; i < rows.length; i++) { | |||
rows[i].onmouseenter = function (event) { | |||
for (var j = 0; j < rows.length; j++) { | |||
let preversion = this.textContent; | |||
let version = preversion.slice(0,5) | |||
let compareVersion = rows[j].textContent.slice(0,5) | |||
if (compareVersion === version) continue | |||
rows[j].className += " other"; | |||
} | |||
}; | |||
rows[i].onmouseleave = function (event) { | |||
var hovers = document.getElementsByClassName('other'); | |||
var len = hovers.length; | |||
for (var j = 0; j < len; j++) { | |||
hovers[0].className = hovers[0].className.replace(/\sother(\s|$)/, ''); | |||
} | |||
}; | |||
} | |||
// this.ele.addEventListener("click", (e) => { | |||
// console.log(e.target) | |||
// }); | |||
} | |||
} | |||
</source> | |||
* How to Create Database Seed Data. | |||
* How to structure code so controller code is in one file and HTML Heex is in another | |||
* How to Use Generators in a Real World Project | |||
* How to Work with Database Data via Basic Commands and Custom Change sets. | |||
* How to Render Database Data to LiveView (via Context Commands). | |||
* How to Create a Chat using PubSub. | |||
* How to Create Event Handlers in LiveView | |||
* How to Create a Dynamic Route from Scratch. | |||
* How to Use Elixirs Template language | |||
* How to Work with Modal | |||
* How to Use CSS From Scratch (Without bundler) | |||
* How to use JS hooks | |||
* Dissect Table Component and Update it to Order List in Reverse. | |||
Selenium | |||
https://www.builder.io/blog/debug-nodejs | |||
https://marketplace.visualstudio.com/items?itemName=ms-vscode.live-server#running-the-extension | |||
This was added recently (see microsoft/vscode#109276). | |||
1. Open the palette (Ctrl + Shift + P) | |||
2. Select "Simple Browser: Preview" | |||
3. Enter web address | |||
_______________________ | |||
<source> | |||
const {By, Key, Builder} = require("selenium-webdriver"); | |||
const assert = require("assert") | |||
require("chromedriver"); | |||
async function test_case(){ | |||
let driver = await new Builder().forBrowser("chrome").build(); | |||
await driver.get("sie"); | |||
await driver.findElement(By.name('password')).clear(); | |||
await driver.findElement(By.name('identification')).clear(); | |||
await driver.findElement(By.name('identification')).sendKeys("bill"); | |||
await driver.findElement(By.name('password')).sendKeys("password", Key.RETURN); | |||
setTimeout(()=>{ | |||
driver.findElement(By.className("req_lan_license")).click(); | |||
},5000) | |||
</source> | |||
<source> | |||
defmodule AppWeb.PageLive do | |||
use AppWeb, :live_view | |||
alias App.Testbeds | |||
def mount(_params, _session, socket)do | |||
{:ok, assign(socket, testbeds: Testbeds.list_testbeds())} | |||
end | |||
def button_event("invoke_editing") do | |||
%JS{} | |||
|> JS.remove_class("active", to: "#editing_button") | |||
|> JS.add_class("hidden", to: "#editing_button") | |||
|> JS.remove_class("hidden", to: "#preview_button") | |||
|> JS.add_class("active", to: "#preview_button") | |||
|> JS.remove_class("active", to: "#preview") | |||
|> JS.add_class("hidden", to: "#preview") | |||
|> JS.remove_class("hidden", to: "#editing") | |||
|> JS.add_class("active", to: "#editing") | |||
end | |||
def button_event("invoke_preview") do | |||
%JS{} | |||
|> JS.remove_class("active", to: "#preview_button") | |||
|> JS.add_class("hidden", to: "#preview_button") | |||
|> JS.remove_class("hidden", to: "#editing_button") | |||
|> JS.add_class("active", to: "#editing_button") | |||
|> JS.remove_class("active", to: "#editing") | |||
|> JS.add_class("hidden", to: "#editing") | |||
|> JS.remove_class("hidden", to: "#preview") | |||
|> JS.add_class("active", to: "#preview") | |||
end | |||
def handle_event("send", params, socket) do | |||
hide_modal("notes-modal-0") | |||
{:noreply, socket} | |||
end | |||
def render(assigns) do | |||
~H""" | |||
<%= for testbed <- @testbeds do %> | |||
<.modal id={"notes-modal-#{testbed.id}"}> | |||
<.button phx-click={button_event("invoke_editing")} id="editing_button" class="hidden"> | |||
Preview | |||
</.button> | |||
<.button phx-click={button_event("invoke_preview")} id="preview_button"> | |||
Edit Me | |||
</.button> | |||
<div id="editing"> PREVIEWING THE WORK</div> | |||
<form phx-submit = "send" id="preview" class="hidden"> | |||
<textarea value = {testbed.note}></textarea> | |||
<.button type="submit" phx-click={hide_modal("notes-modal-#{testbed.id}")}>SAVE WORK</.button> | |||
</form> | |||
</.modal> | |||
<%= if testbed.note do %> | |||
<div phx-click={show_modal("notes-modal-#{testbed.id}")}>READ ME ...<%= testbed.note %></div> | |||
<% end %> | |||
<% end %> | |||
""" | |||
end | |||
end | |||
</source> | |||
<source> | |||
import {HfInference} from "@huggingface/inference"; | |||
import dotenv from "dotenv"; | |||
dotenv.config() | |||
const HF_ACCESS_TOKEN = process.env.HF_ACCESS_TOKEN | |||
const hf = new HfInference(HF_ACCESS_TOKEN); | |||
/*______Uncomment for this example______________________ | |||
const model = "nlpconnect/vit-gpt2-image-captioning"; | |||
const imageURL = "https://i.imgur.com/lTvb7Et.png"; | |||
const response = await fetch(imageURL); | |||
const imageBlob = await response.blob(); | |||
const result = await hf.imageToText({ | |||
data: imageBlob, | |||
model: model, | |||
}); | |||
_______________________________________________________*/ | |||
/*________Another example ___________________*/ | |||
const result = await hf.summarization({ | |||
model: 'facebook/bart-large-cnn', | |||
inputs: "The role of a dumb man is to get smarter oogy bookie boo", | |||
parameters:{ | |||
max_length: 100 | |||
} | |||
}); | |||
console.log(result); | |||
</source> | |||
________________________________________ | |||
https://fly.io/phoenix-files/sdeb-toggling-element/ | |||
https://www.youtube.com/watch?v=vBgZvQapqhs | |||
https://blog.testdouble.com/posts/2022-11-28-how-to-use-javascript-with-phoenix-liveview/ | |||
https://hexdocs.pm/phoenix_live_view/0.19.3/Phoenix.Component.html | |||
_______________________________________ | |||
https://www.smashingmagazine.com/2011/10/quick-look-math-animations-javascript/ | |||
* Depth first traversal | |||
* Breadth first traversal | |||
https://dev.to/codesphere/10-algorithms-every-developer-should-learn-3lnm | |||
https://www.youtube.com/watch?v=fPz40W9mfCg | |||
__________________________________ | |||
<source> | |||
defmodule AppWeb.GroupLive do | |||
use AppWeb, :live_view | |||
alias App.TestBeds | |||
alias App.Groups | |||
def mount(_params, _session, socket) do | |||
{:ok, assign(socket, testbeds: TestBeds.list_testbeds(), groups: Groups.list_groups())} | |||
end | |||
def handle_event("select-group", params, socket) do | |||
IO.inspect params | |||
{:noreply, socket} | |||
end | |||
def render(assigns) do | |||
~H""" | |||
<div class="grid grid-columns-2 grid-flow-col gap-4"> | |||
<%= for testbed <- @testbeds do %> | |||
<div class="column-1"> | |||
<div class="name"><%= testbed.name %></div> | |||
</div> | |||
<div class="column-2"> | |||
<form phx-change="select-group" > | |||
<input type="hidden" name="testbed_id" value={testbed.id}/> | <input type="hidden" name="testbed_id" value={testbed.id}/> | ||
<select id="group-selection" name="group-id" class=""> | <select id="group-selection" name="group-id" > | ||
<%= for group <- @groups do %> | |||
<%= for group <- updated_groups(testbed.id) do %> | <option value= {"#{group.id}"} ><%=group.name %></option> | ||
<% end %> | |||
</select> | |||
</form> | |||
</div> | |||
<% end %> | |||
</div> | |||
""" | |||
end | |||
end | |||
</source> | |||
____________________________ | |||
<source> | |||
defmodule AppWeb.GroupLive do | |||
use AppWeb, :live_view | |||
alias App.TestBeds | |||
alias App.Groups | |||
def mount(_params, _session, socket) do | |||
{:ok, assign(socket, testbeds: TestBeds.list_testbeds(), groups: Groups.list_groups(), messages: [])} | |||
end | |||
def handle_event("select-group", params, socket) do | |||
group = App.Groups.get_group!(params["group-id"]) | |||
# IO.inspect group | |||
testbed = App.TestBeds.get_test_bed!(params["testbed_id"]) | |||
App.TestBeds.update_test_bed(testbed, %{group_id: group.id}) | |||
{:noreply, socket} | |||
end | |||
def updated_groups(testbed_id) do | |||
groups = App.Groups.list_groups | |||
tail = Enum.filter(groups, fn(item)-> item.id !== testbed_id end) | |||
head = Enum.filter(groups, fn(item)-> item.id == testbed_id end) | |||
|>Enum.at(0) | |||
[head | tail] | |||
end | |||
# def handle_event("groups", params, socket)do | |||
# selected_testbed = %{name: "my testbed name", id: 3} | |||
# groups = App.Groups.list_groups | |||
# tail = Enum.filter(groups, fn(item)-> item.id !== selected_testbed.id end) | |||
# head = Enum.filter(groups, fn(item)-> item.id == selected_testbed.id end) | |||
# |>Enum.at(0) | |||
# # result = Enum.filter([1, 2, 3], fn x -> x.id !== 1 end) | |||
# IO.inspect [head | tail] | |||
# # | |||
# {:noreply, socket, assigns.message:} | |||
# end | |||
def render(assigns) do | |||
~H""" | |||
<%!-- <div phx-click = "groups">CLICK ME</div> --%> | |||
<%= for testbed <- @testbeds do %> | |||
<div class="grid grid-cols-4 gap-4 p-8 rounded shadow bg-white"> | |||
<h2> Name</h2> | |||
<div class="name"><%= testbed.name %> </div> | |||
<h2> Group</h2> | |||
<div> | |||
<form phx-change="select-group" name="testbed_id" > | |||
<input type="hidden" name="testbed_id" value={testbed.id}/> | |||
<select id="group-selection" name="group-id" class=""> | |||
<%= for group <- updated_groups(testbed.id) do %> | |||
<option value= {"#{group.id}"} ><%=group.name %></option> | |||
<!-- Write code to reorder with testbed.group_id matching group_id --> | |||
<% end %> | |||
</select> | |||
</form> | |||
</div> | |||
</div> | |||
<% end %> | |||
""" | |||
end | |||
end | |||
</source> | |||
_____________________________________ | |||
<source> | |||
defmodule AppWeb.PageLive do | |||
use AppWeb, :live_view | |||
alias App.Testbeds | |||
alias App.Groups | |||
def mount(_params, _session, socket) do | |||
{:ok, assign(socket, testbeds: Testbeds.list_testbeds(), groups: Groups.list_groups())} | |||
end | |||
def handle_event("select-group", params, socket) do | |||
group = App.Groups.get_group!(params["group-id"]) | |||
testbed = App.Testbeds.get_testbed!(params["testbed_id"]) | |||
result = App.Testbeds.update_testbed(testbed, %{group_id: group.id}) | |||
IO.inspect result | |||
{:noreply, socket} | |||
end | |||
def updated_groups(testbed_id) do | |||
testbed = App.Testbeds.get_testbed!(testbed_id) | |||
groups = App.Groups.list_groups | |||
tail = Enum.filter(groups, fn(item)-> item.id !== testbed.group_id end) | |||
head = Enum.filter(groups, fn(item)-> item.id == testbed.group_id end) | |||
|>Enum.at(0) | |||
[head | tail] | |||
end | |||
def render(assigns) do | |||
~H""" | |||
<%= for testbed <- @testbeds do %> | |||
<div class="grid grid-cols-4 gap-4 p-8 rounded shadow bg-white"> | |||
<h2> Name</h2> | |||
<div class="name"><%= testbed.name %> </div> | |||
<h2> Group</h2> | |||
<div> | |||
<form phx-change="select-group" name="testbed_id" > | |||
<input type="hidden" name="testbed_id" value={testbed.id}/> | |||
<select id="group-selection" name="group-id" class=""> | |||
<%= for group <- updated_groups(testbed.id) do %> | |||
<%!-- <option value= {"#{group.id}"}><%=group.name %></option> --%> | |||
<!-- Write code to reorder with testbed.group_id matching group_id --> | |||
<%= if group !== nil do %> | |||
<option value= {"#{group.id}"}><%=group.name %></option> | |||
<% end %> | |||
<% end %> | |||
</select> | |||
</form> | |||
</div> | |||
</div> | |||
<% end %> | |||
""" | |||
end | |||
end | |||
</source> | |||
<source> | |||
has_many :testbeds, TestBed, on_delete: :restrict # Has many | |||
</source> | |||
https://elixirforum.com/t/ecto-has-many-on-delete-protect/49550 | |||
https://hexdocs.pm/ecto/Ecto.Changeset.html#unique_constraint/3 | |||
____________________________________________________________________ | |||
== How to Create a Mix Project and Use It in Another Mix Project == | |||
To create and use a **Mix project** inside another **Mix project** in Elixir, follow the steps below: | |||
=== 1. Create a New Mix Project === | |||
First, you need to create a new Mix project. Let's call this project `library_project`. Run the following command: | |||
<pre> | |||
mix new library_project --module LibraryProject | |||
</pre> | |||
This will create a new Elixir Mix project named `library_project`. The `--module` flag specifies the name of the module that will be created (in this case `LibraryProject`). | |||
The directory structure of `library_project` will look like this: | |||
<pre> | |||
library_project/ | |||
|_ lib/ | |||
|_ library_project.ex | |||
|_ mix.exs | |||
</pre> | |||
You can then add some basic functionality inside the `library_project/lib/library_project.ex` file: | |||
<pre> | |||
defmodule LibraryProject do | |||
def hello do | |||
IO.puts "Hello from LibraryProject!" | |||
end | |||
end | |||
</pre> | |||
Now, `library_project` is ready to be used by another Mix project. | |||
=== 2. Create a Second Mix Project === | |||
Next, create the second Mix project where you want to use `library_project`. For example: | |||
<pre> | |||
mix new app_project --module AppProject | |||
</pre> | |||
This will create another Elixir project called `app_project`. | |||
The directory structure for this project will look like: | |||
<pre> | |||
app_project/ | |||
|_ lib/ | |||
|_ app_project.ex | |||
|_ mix.exs | |||
</pre> | |||
=== 3. Add `library_project` as a Dependency in `app_project` === | |||
Now, you want to make `library_project` available in `app_project`. There are two main ways to do this: | |||
==== Option 1: Local Dependency (via `path`) ==== | |||
If `library_project` is a local project (i.e., it's stored in a different directory), you can add it to the `mix.exs` of `app_project` as a local dependency by specifying the path to `library_project`. | |||
Edit the `mix.exs` of `app_project`: | |||
<pre> | |||
defmodule AppProject.MixProject do | |||
use Mix.Project | |||
def project do | |||
[ | |||
app: :app_project, | |||
version: "0.1.0", | |||
elixir: "~> 1.14", | |||
start_permanent: Mix.env() == :prod, | |||
deps: deps() | |||
] | |||
end | |||
defp deps do | |||
[ | |||
{:library_project, path: "../library_project"} # Add the local dependency | |||
] | |||
end | |||
end | |||
</pre> | |||
Here, `{:library_project, path: "../library_project"}` tells Mix to use the local `library_project` located in the `../library_project` directory (relative to `app_project`). | |||
After updating the `mix.exs`, run the following command: | |||
<pre> | |||
cd app_project | |||
mix deps.get | |||
</pre> | |||
This will fetch the dependency and compile `library_project`. | |||
==== Option 2: Publish and Fetch as a Hex Package ==== | |||
If you want to use `library_project` as a package in another project (for example, by publishing it to **Hex.pm**, Elixir’s package manager), you need to publish it to Hex and then add it as a dependency. | |||
1. Publish `library_project` to Hex (if it's public or for your private use). | |||
2. Add `library_project` to your `app_project`’s `mix.exs` dependencies: | |||
<pre> | |||
defp deps do | |||
[ | |||
{:library_project, "~> 0.1.0"} # Specify the published version from Hex | |||
] | |||
end | |||
</pre> | |||
Run `mix deps.get` to fetch the package from Hex. | |||
=== 4. Use `library_project` in `app_project` === | |||
Now, you can use the functionality from `library_project` inside your `app_project`. For example, in `app_project/lib/app_project.ex`, you can call the function from `library_project`: | |||
<pre> | |||
defmodule AppProject do | |||
def start do | |||
IO.puts "Starting app_project!" | |||
LibraryProject.hello() # Call the function from the library_project | |||
end | |||
end | |||
</pre> | |||
=== 5. Run the App === | |||
Finally, you can run your `app_project`: | |||
<pre> | |||
cd app_project | |||
mix run -e "AppProject.start()" | |||
</pre> | |||
This will print: | |||
<pre> | |||
Starting app_project! | |||
Hello from LibraryProject! | |||
</pre> | |||
== Recap of the Process == | |||
1. **Create the `library_project`**: Use `mix new` to generate a library project. | |||
2. **Create the `app_project`**: Create the second project where you will use the library. | |||
3. **Add the `library_project` as a dependency**: | |||
- Use the `path` option to point to the local project or publish it to Hex for public access. | |||
4. **Use the `library_project` in the `app_project`**: Call functions or use modules from the library inside the second project. | |||
5. **Run the app**: Use `mix run` to execute the project. | |||
This way, you can reuse code from one Mix project in another by adding it as a dependency. | |||
==z== | |||
Overloaded functions are extremely common in Elixir applications. They are | |||
regularly used for recursive functions and for changing behavior based on | |||
configuration. | |||
Revision as of 23:21, 20 February 2025
from(a in Article, order_by: [desc: a.inserted_at], # Assuming 'inserted_at' is the timestamp for when the article was created or updated limit: 8 ) |> Repo.all()
defmodule AppWeb.Landing do
use AppWeb, :live_view
alias App.Testbeds
alias App.Testbeds.Testbed
alias App.Repo # Ensure to alias Repo
import Ecto.Query # Import Ecto.Query to work with queries
def mount(_params, _session, socket) do
# Default to ordering by name
testbeds = Repo.all(from(t in Testbed, order_by: t.name))
# Assign the testbeds to the socket for rendering
{:ok, assign(socket, testbeds: testbeds, sort: :name)}
end
def render(assigns) do
~H"""
<h1>Hello World! YAY</h1>
<div>
<!-- Buttons to toggle sorting -->
<button phx-click="sort_by_name">Sort by Name</button>
<button phx-click="sort_by_updated_at">Sort by Updated By</button>
</div>
<ul>
<%= for testbed <- @testbeds do %>
<li><%= testbed.name %></li>
<% end %>
</ul>
"""
end
def handle_event("sort_by_name", _params, socket) do
testbeds = Repo.all(from(t in Testbed, order_by: t.name))
{:noreply, assign(socket, testbeds: testbeds, sort: :name)}
end
def handle_event("sort_by_updated_at", _params, socket) do
testbeds = Repo.all(from(t in Testbed, order_by: t.inserted_at))
{:noreply, assign(socket, testbeds: testbeds, sort: :inserted_at)}
end
end
_______________________________________________________
- Create a form with a post request.
- Create the route needed for that request
- Create the controller needed for that route
Form
<.form action={~p"/todos"} method="post" class="mt-10 space-y-8">
<div>
<label for="contact_name" class="block text-sm font-semibold leading-6 text-zinc-800">
Name
</label>
<input
id="todo"
type="text"
name="todo"
class="mt-2 block w-full rounded-lg text-zinc-900 focus:ring-0 sm:text-sm sm:leading-6 border-zinc-300 focus:border-zinc-400"
/>
</div>
<button
type="submit"
class="rounded-lg bg-zinc-900 hover:bg-zinc-700 py-2 px-3 text-sm font-semibold leading-6 text-white active:text-white/80"
>
Save Contact
</button>
</.form>
Controller
# In your TodoController
defmodule AppWeb.TodoController do
use AppWeb, :controller
# Action to handle the form submission (POST request)
def create(conn, %{"todo" => todo_name}) do
# Here you can handle the form data (e.g., save it to the database)
IO.puts("Received Todo: #{todo_name}")
# For now, let's just redirect to the home page
conn
|> put_flash(:info, "Todo saved!")
|> redirect(to: "/")
end
end
________________________________________________________
- CRUD Forms
To understand CRUD forms you will need to understand the following concepts.
- Ecto Schema
- Ecto Context
- Form Basics
- form/1
- simple_form/1
- changesets
We are not going to go in order.
The Context provides CRUD functions for the chosen data schema. Phoenix contexts define the interface through which other parts of the codebase can interact with the app layer.
_______________________
- Create routes
- create page with form (submit get request)
(ecto schema, context, changesets)
_______________________________________________________
In that case, you really should switch it to prod mode. Instead of doing all that above with the "config/secret.exs" file, create the ".env" in the project root, then add DotenvParser.load_file(".env") to "config/runtime.exs" as the first line inside the if config_env() == :prod do. In the .env file, set the DATABASE_URL and SECRET_KEY_BASE. Then run it as MIX_ENV=prod mix phx.server.
def get_titles do
urls = [
"https://cointelegraph.com/rss",
"https://chaski.huffpost.com/us/auto/vertical/us-news"
# Add more RSS feed URLs here
]
urls
|> Enum.map(fn url ->
with {:ok, feed} <- ElixirRss.fetch_and_parse(url) do
Enum.map(feed.entries, & &1.title)
else
{:error, reason} -> {:error, reason}
end
end)
end
defmodule App do
def get_titles() do
data = [
"https://cointelegraph.com/rss",
"https://chaski.huffpost.com/us/auto/vertical/us-news"
# Add more RSS feed URLs here
]
data
|> Enum.map(fn url ->
case ElixirRss.fetch_and_parse(url) do
{:ok, feed} -> feed.entries |> Enum.map(fn entry -> entry end)
{:error, reason} -> {:error, reason}
end
end)
|> Enum.map(fn entry_list ->
entry_list
|> Enum.map(fn entry -> entry.title end)
end)
end
def hello do
rss_feeds = [
"https://cointelegraph.com/rss",
"https://chaski.huffpost.com/us/auto/vertical/us-news"
# Add more RSS feed URLs here
]
rss_feeds
|> Enum.map(fn url ->
case ElixirRss.fetch_and_parse(url) do
{:ok, feed} -> feed.entries |> Enum.map(fn entry -> entry end)
{:error, reason} -> {:error, reason}
end
end)
end
end
defmodule AppWeb.PageLive do
use AppWeb, :live_view
def mount(_params, _session, socket) do
if connected?(socket) do
Phoenix.PubSub.subscribe(App.PubSub, "message")
end
{:ok, assign(socket, message_item: "different")}
end
def handle_info({:pubsub_transmission, text}, socket) do
IO.inspect "pub sub working"
{:noreply, assign(socket, message_item: text)}
end
def loop(data) do
IO.inspect data
if data > 0 do
IO.inspect "notify"
# run code to compare files, loop. If different run pubsub code below
:timer.sleep(2000)
loop(data-1)
end
if data <= 0 do
Phoenix.PubSub.broadcast(App.PubSub, "message", {:pubsub_transmission, "data"})
"done"
end
end
def handle_event("change-text", _params, socket) do
spawn(fn -> loop(3) end)
{:noreply, assign(socket, message_item: "START" )}
end
def render(assigns) do
# AppWeb.PageLive.loop("xxx")
~H"""
SANDBOX
<.button phx-click="change-text" value={@message_item}>CLICK ME</.button>
<div>
<%= @message_item %>
</div>
"""
end
end
elixir : 1.16.2
Erl: 20
- PostGres
Created Saturday 18 February 2023
Launch [PostGres](#PostGres) from Terminal
sudo -u postgres psql
Set User Permission
<https://commandprompt.com/education/how-to-create-a-superuser-in-postgresql/>
ALTER USER user_name WITH PASSWORD 'new_password';
Run Post Gres Commands
sudo -i -u postgres psql
Create and Delete Database
CREATE DATABASE name; DROP DATABASE name;
- View user list
\du
View all databases
postgres=# \l
Change databases
postgres=# \c name-of-database
View Tables
After you change databases and are selected on one of them do this:
\dt
SELECT * FROM mytablename;
EMPTY TABLE DELETE FROM name-of-table
CREATE TABLE
CREATE TABLE table_name(
column1 datatype, column2 datatype, column3 datatype, ..... columnN datatype, PRIMARY KEY( one or more columns )
);
INSERT
INSERT INTO ITEMS(id, name) VALUES(1,'htvjbjbgni'); Strings are single quote
Roadmap
defmodule AppWeb.ItemsLive do
use AppWeb, :live_view
def mount(_params, _session, socket) do
{:ok, assign(socket, switches: ["grindy","sonic","ogre"])}
end
def handle_event("run",_params,socket) do
IO.inspect "DOWNLOAD VIA SSH"
{:noreply, redirect(socket, to: "/download")}
end
def render(assigns) do
~H"""
<ul>
<%= for switch <- @switches do %>
<a phx-submit="click" href="/download"> <li> <%= switch %> </li> </a>
<%end%>
</ul>
"""
end
end
Notes
List of Switches and the option to select the one needed. Button to download JSON file. Drag and Drop Palette to upload JSON file.
After JSON file is uploaded a button titled "Make Active" is available.
Changeset notes and forms
NOTE: Use case statements for pattern matching ____________________________________________
Create a basic <.form></form> without error handling
<.form :let={f} for={@changeset} action={~p"/items"} >
<.input field={f[:title]} type="text" label="Title" />
<.button>Save Item</.button>
</.form>
With error handling looks like this:
<.form :let={f} for={@changeset} action={~p"/items"} >
<.error :if={@changeset.action}>
Oops, something went wrong! Please check the errors below.
</.error>
<.input field={f[:title]} type="text" label="Title" />
<.button>Save Item</.button>
</.form>
Create the routes for render and post to endpoint.
The controller for the page render needs to pass in the changeset via ```Items.change_item(%Item{})```
def new(conn, _params) do
changeset = Items.change_item(%Item{})
render(conn, :new, changeset: changeset)
end
Post route without error handling
def create(conn, %{"item" => item_params}) do
case Items.create_item(item_params) do
{:ok, item} ->
conn
|> put_flash(:info, "Item created successfully.")
|> redirect(to: ~p"/items/#{item}")
end
end
With error handling
def create(conn, %{"item" => item_params}) do
case Items.create_item(item_params) do
{:ok, item} ->
conn
|> put_flash(:info, "Item created successfully.")
|> redirect(to: ~p"/items/#{item}")
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, :new, changeset: changeset)
end
end
_____________________________________________
https://medium.com/@vincentlin/phoenix-very-simple-form-c4b121697fcb
home.html.heex
<form action="/submit" method="post">
<label for="name">Name:</label><br>
<input type="text" id="name" name="name"><br>
<input type="hidden" name="_csrf_token" value={@csrf_token} />
<input type="submit" value="Submit">
</form>
page_controller.ex
defmodule AppWeb.PageController do
use AppWeb, :controller
def home(conn, _params) do
# The home page is often custom made,
csrf_token = Plug.CSRFProtection.get_csrf_token()
render(conn, :home, csrf_token: csrf_token)
end
def submit(conn, _params) do
# The home page is often custom made,
IO.inspect(_params)
IO.inspect "___________________________"
csrf_token = Plug.CSRFProtection.get_csrf_token()
render(conn, :home, csrf_token: csrf_token)
end
end
router.ex
scope "/", AppWeb do
pipe_through :browser
post "/submit", PageController, :submit
get "/", PageController, :home
end
Ecto.Adapters.SQL.query(Repo, "select * from artists where id=1")
Ecto cheatsheet: https://hexdocs.pm/ecto/crud.html __________________________________________________________________ Most programming languages give you the ability to set global state by creating globally scoped variables. Elixir does not do this. Instead, Elixir provides a tool called a "processes" to persist state. Elixir applications are composed of these "processes".
Processes by themselves are complicated to use, thus Elixir provides abstractions to make working with processes easier. One of these abstractions is called a GenServer.
GenServers are not only used to store state, they are also used to invoke work independent of the main application process.
Creating a GenServer to Store State
To Create a GenServer, start by creating a basic Module that uses the GenServer module (image below)
defmodule App do use GenServer end
We now need to describe the actions we want to perform. Being that this is used to store state, we will use basic CRUD actions: Create, Read, Update, Delete. To begins, empty functions describing each action is created.
defmodule App do def create do end def read do end def update do end def delete do end end
What now?
To fill in the functions with the appropriate code we must first have a basic understanding of how GenServers are structured.
GenServers are composed of the GenServer module invoking callbacks. The callbacks have built in names that you, as a developer have to use.
These callbacks are named:
- init
- handle_cast
- handle_call
To demonstrate, here is an example :
{:ok, pid} = GenServer.start(ModuleNameOfGenServer, %{})
The above function named "start" calls the function named init in the Module below. In GenServer syntax the name of the GenServer functions always invoke callbacks that are determined by the language.
defmodule ModuleNameOfGenServer do
# 1
def init(state \\ %{}) do
{:ok, state}
end
This is a list of the GenServer functions and their corresponding callbacks.
- GenServer.start/1 --> init/1
- GenServer.call/3 --> def handle_call/3
- GenServer.cast/2 --> handle_cast/2
Start and Call
Call and Cast can be used to perform the same operations. The
__________________________________________________________ Genserver timed events
defmodule Go do
use GenServer
def go() do
GenServer.start_link(__MODULE__, [])
end
def init(list) do
:timer.apply_interval(1000, __MODULE__, :add, [self(), "weeeee"])
{:ok, list}
end
def view(pid) do
GenServer.call(pid, :view)
end
def handle_call(:view, _from, list) do
{:reply, list, list}
end
def add(pid, new_item) do
GenServer.call(pid, {:item, new_item})
end
def handle_call({:item, new_item}, _from, state) do
result = [new_item] ++ state
{:reply, result, result}
end
end
defmodule ShoppingList do
use GenServer
def start_link() do
GenServer.start_link(ShoppingList, [])
end
def init(list) do
{:ok, list}
end
def view(pid) do
GenServer.call(pid, :view)
end
def handle_call(:view, _from, list) do
{:reply, list, list}
end
def add(pid, new_item) do
GenServer.call(pid, {:item, new_item})
end
def handle_call({:item, new_item}, _from, state) do
result = [new_item] ++ state
{:reply, result, result}
end
end
https://blog.appsignal.com/2018/06/12/elixir-alchemy-deconstructing-genservers.html
defmodule KeyValue do
# 1
def init(state \\ %{}) do
{:ok, state}
end
# 2
def handle_cast({:put, key, value}, state) do
{:noreply, Map.put(state, key, value)}
end
# 3
def handle_call({:get, key}, _from, state) do
{:reply, Map.fetch!(state, key), state}
end
end
# {:ok, pid} = GenServer.start(KeyValue, %{})
# GenServer.cast(pid, {:put, :foo, "bar"})
# GenServer.call(pid, {:get, :foo})
Send messages via these functions wrapped in module functions .You can look at these are functions used to fire "GenServer Events".
- start_link
- GenServer.call
- GenServer.cast
The corresponding server functions that reply are:
- init
- handle__call
- handle_cast
These can be looked at as "listeners" - in JavaScript parlance. __________________________________________
If you have experience with programming languages other than Elixir the idea of GenServers may be very foreign and require a strong context shift to learn. This document is intended to simplify the concept.
- What: GenServers are simply a way to hold state and/or invoke actions based on incoming events.
- when
- where
- why
- how
___________________________________________ GenServer timer example
defmodule Timer do
use GenServer
def init(_) do
:timer.send_interval(1000, :xyz)
{:ok, 0}
end
def handle_info(:xyz, state) do
IO.inspect(state)
{:noreply, state + 1}
end
end
[
{
data:{
creationTime,
publishDate,
}
featured:{
setAmount,
items:[]
},
children:{
setAmount,
items:[]
},
links:{
setAmount,
items:[]
}
}
]
jason.encode / jason.decode
def run(num) do
Enum.each(0..99999, fn(_x) ->
task = Task.async(fn -> num + _x end)
IO.inspect Task.await(task)
end)
Enum.map(feed.entries, fn x -> %{url: x.url, title: x.title} end)
App is split:
App that captures RSS in UI and lets admin select stories. THere are settings to determine the number of FEATURED,CHILD and LINK stories. News categories are horizontal and scrollable. This exports a json doc with all RSS data for each story. The
App that reads the json data. This app creates tables and is the front end.
https://stephenbussey.com/tags/elixir.html
defmodule AppWeb.GroupLive do
use AppWeb, :live_view
alias App.TestBeds
alias App.Groups
def mount(_params, _session, socket) do
{:ok, assign(socket, testbeds: TestBeds.list_testbeds(), groups: Groups.list_groups())}
end
def handle_event("select-group", params, socket) do
group = App.Groups.get_group!(params["group-id"])
testbed = App.TestBeds.get_test_bed!(params["testbed_id"])
App.TestBeds.update_test_bed(testbed, %{group_id: group.id})
{:noreply, socket}
end
def updated_groups(testbed_id) do
testbed = App.TestBeds.get_test_bed!(testbed_id)
groups = App.Groups.list_groups
tail = Enum.filter(groups, fn(item)-> item.id !== testbed.group_id end)
head = Enum.filter(groups, fn(item)-> item.id == testbed.group_id end)
|>Enum.at(0)
[head | tail]
end
def render(assigns) do
~H"""
<%= for testbed <- Enum.sort_by(@testbeds , &("#{&1.name}"), :asc)do %>
<div class="grid grid-cols-4 gap-4 p-8 rounded shadow bg-white">
<h2> Name</h2>
<div class="name"><%= testbed.name %> </div>
<h2> Group</h2>
<div>
<form phx-change="select-group" name="testbed_id" >
<input type="hidden" name="testbed_id" value={testbed.id}/>
<select id="group-selection" name="group-id" class="">
<%= for group <- updated_groups(testbed.id) do %>
<%= if group !== nil do %>
<option value= {"#{group.id}"}><%=group.name %></option>
<%else%>
<option value= {"Unassigned"}>NONE</option>
<% end %>
<% end %>
</select>
</form>
</div>
</div>
<% end %>
"""
end
end
Developer Notes
Groups Feature
To complete this feature do these things.
- Run through the steps in | this document to create Group table.
- Groups have these fields name: string, description: string and color: string
- Add unique constraint in migration file:
def change do
create unique_index(:groups, [:name])
end
- Port over Group LiveView code (temporarily placed at discussion page).
- On completion possibly, create a Group named No Group and set all Testbeds to it.
________
https://blog.appsignal.com/2022/10/11/phoenix-liveview-018-new-special-html-attributes.html
https://blixtdev.com/whats-new-phoenix-1-7/
https://github.com/devato/inertia_phoenix
https://phoenixonrails.com/blog/you-can-stop-using-form-for-in-phoenix
https://samuelmullen.com/articles/phoenix_templates_rendering_and_layouts
WorkFlow of Todo App in Both LiveView and DeadView.
Note:
Create foreign key data. Create live view. List all testbeds and form to assign each a group by group-name.
Repeat in dead view
Mental model and data flows
home.html.heex
<a href="/data" name="we are the world" > click me</a>
<%= @message %>
page controller
defmodule AppWeb.PageController do
use AppWeb, :controller
def home(conn, _params) do
IO.inspect _params
render(conn, :home, message: "Hello")
end
end
get "/:yay", PageController, :index
get "/", PageController, :index
- Explain control flow of "Dead Views" and the Generator HTML Control flow.
- Proper Way to use Modal
- How to give Focus to Fields in Modal (or other forms fields)
- How to Create Database Seed Data.
- How to structure code so controller code is in one file and HTML Heex is in another
- How to Use Generators in a Real World Project
- How to Work with Database Data via Basic Commands and Custom Change sets.
- How to Render Database Data to LiveView (via Context Commands).
- How to Create a Chat using PubSub.
- How to Create Event Handlers in LiveView
- How to Create a Dynamic Route from Scratch.
- How to Use Elixirs Template language
- How to Work with Modal
- How to Use CSS From Scratch (Without bundler)
- How to use JS hooks
______________________ https://studioindie.co/blog/heex-guide/
get "/", PageController, :some-atom #This is the route. some-atom is the controller name
defmodule AppWeb.PageController do
use AppWeb, :controller
def some-atom(conn, _params) do # controller name
# The home page is often custom made,
# so skip the default app layout.
render(conn, :some-atom, layout: false) # :some-atom is template name
end
end
Controllers page_html.ex contains
defmodule AppWeb.PageHTML do use AppWeb, :html embed_templates "page_html/*" end
page_html is the name of the folder in Controllers/page_html
Controllers/page_html/some-atom.html.heex (this is the template)
__________________________________________
Set all association with preload:
def list_groups do
Repo.all(Group)
|> Repo.preload([:testbeds])
end
Insert Child into Parent
Example:
App.get_group!(2) # arg is id |> Ecto.build_assoc(:testbeds) |> Repo.insert() Or group = App.get_group!(2) # arg is id testbeds = Ecto.build_assoc(group, :testbeds) App.Group.create_group(testbeds)
group = App.Groups.get_group!(1)
thing = Ecto.build_assoc(group, :testbeds, name: "DUMB")
alias App.{Repo}
Repo.insert(thing)
Example
avatar = %Avatar{nick_name: "Elixir", pic_url: "http://elixir-lang.org/images/logo.png"}
user = %User{name: "John Doe", email: "john.doe@example.com", avatar: avatar}
- Create Group Table
mix phx.gen.html Groups Group groups name:string
Set the resources /
mix ecto.migrate
For testbeds create a migration file and add this field: group_id:references:groups
def change do
alter table(:testbeds) do # customize to your code
add :group_id, references(:groups)
end
end
Update Groups Schema (Don't forget alias / has_many)
defmodule App.Groups.Group do use Ecto.Schema import Ecto.Changeset alias App.Testbeds.Testbed # Alias! schema "posts" do field :body, :string field :title, :string has_many :testbeds, Testbed # Has many timestamps() end @doc false def changeset(post, attrs) do post |> cast(attrs, [:title, :body]) |> validate_required([:title, :body]) end end
TESTBEDS Schema
defmodule App.Testbeds.Testbed do
use Ecto.Schema
import Ecto.Changeset
alias App.Groups.Group # Alias
schema "testbeds" do
field :name, :string
belongs_to :group, Group # Belongs to
timestamps()
end
@doc false
def changeset(testbed, attrs) do
testbed
|> cast(attrs, [:name])
|> validate_required([:name])
end
end
_____________________________________ Side notes.
defmodule App.Repo.Migrations.CreateTestbeds do
use Ecto.Migration
def change do
create table(:testbeds) do
add :name, :string
add :group_id, references(:groups, on_delete: :nothing)
timestamps() end
create index(:testbeds, [:group_id]) end
end
________________________________
mix phx.gen.context Comments Comment comments name:string content:text post_id:references:posts
mix phx.gen.html Testbeds Testbed testbeds name:string group_id:references:groups
https://graffino.com/web-development/first-steps-in-elixir-and-phoenix-create-a-blog-prototype
https://blog.logrocket.com/getting-started-ecto-phoenix/
https://serokell.io/blog/ecto-guide-for-beginners
mix phx.gen.html Testbeds Testbed testbeds name:string
mix phx.gen.html Groups Group groups name:string
Create migration for
mix ecto.gen.migration add_testbed_group_reference
Migration:
def change do
alter table(:testbeds) do # customize to your code
add :group_id, references(:groups)
end
end
Manually Change:
defmodule App.Group do
use Ecto.Schema
schema "groups" do
field :title, :string
field :tagline, :string
has_many :testbeds, App.Testbed
end
end
testbeds
defmodule App.Repo.Migrations.AddTextbedGroups do
use Ecto.Migration
def change do
create table(:testbeds) do
add :name, :string
add :group_id, references(:groups)
end
end
end
# lib/app/testbeds.ex
defmodule App.Testbed do
use Ecto.Schema
schema "testbedss" do
field :name, :string
belongs_to :group, App.Group
end
end
_____________________
%App.TestBeds.TestBed{
__meta__: #Ecto.Schema.Metadata<:loaded, "testbeds">, id: 1, developer: "None", name: "sdfsd", owner: "sdfsddf", note: "sdsfsddf", status: "Available", url: "sdf", version: "ddf", manager: "sf", inserted_at: ~N[2023-08-21 13:45:39], updated_at: ~N[2023-08-21 13:45:44]
} ________________________________________________________________---
defmodule AppWeb.SendLive do
use AppWeb, :live_view
def mount(_params, _session, socket) do
{:ok, socket}
end
def handle_event("send", %{"text" => text}, socket) do
AppWeb.Endpoint.broadcast(topic, "message", text)
{:noreply, socket}
end
defp topic do
"chat"
end
def render(assigns)do
~H"""
<div>
<h1>Send Message</h1>
<form phx-submit="send">
<input type="text" name="text" />
<button type="submit">Send</button>
</form>
</div>
"""
end
end
______________________________________________________________
defmodule AppWeb.ReceiveLive do
use AppWeb, :live_view
def mount(_params, _session, socket) do
if connected?(socket) do
AppWeb.Endpoint.subscribe(topic)
end
{:ok, assign(socket, messages: "")}
end
def handle_info(%{event: "message", payload: message}, socket) do
IO.inspect message
{:noreply, assign(socket, messages: message)}
end
defp topic do
"chat"
end
def render(assigns)do
~H"""
<div>
<h1>ChatLive</h1>
<%= @messages %>
</div>
"""
end
end
______________________________________________________________ When you hover over a list of items this hook finds items with the same name and sets the opacity of all others to 0. You see all the items similar to the one you are hovering over.
TestbedVersionHover: {
// https://jsfiddle.net/IrvinDominin/7K2Z3/
mounted() {
let rows = document.getElementsByClassName("testbed-version")
// this.ele = element
// rows = this.ele;
for (var i = 0; i < rows.length; i++) {
rows[i].onmouseenter = function (event) {
for (var j = 0; j < rows.length; j++) {
let preversion = this.textContent;
let version = preversion.slice(0,5)
let compareVersion = rows[j].textContent.slice(0,5)
if (compareVersion === version) continue
rows[j].className += " other";
}
};
rows[i].onmouseleave = function (event) {
var hovers = document.getElementsByClassName('other');
var len = hovers.length;
for (var j = 0; j < len; j++) {
hovers[0].className = hovers[0].className.replace(/\sother(\s|$)/, '');
}
};
}
// this.ele.addEventListener("click", (e) => {
// console.log(e.target)
// });
}
}
- How to Create Database Seed Data.
- How to structure code so controller code is in one file and HTML Heex is in another
- How to Use Generators in a Real World Project
- How to Work with Database Data via Basic Commands and Custom Change sets.
- How to Render Database Data to LiveView (via Context Commands).
- How to Create a Chat using PubSub.
- How to Create Event Handlers in LiveView
- How to Create a Dynamic Route from Scratch.
- How to Use Elixirs Template language
- How to Work with Modal
- How to Use CSS From Scratch (Without bundler)
- How to use JS hooks
- Dissect Table Component and Update it to Order List in Reverse.
Selenium https://www.builder.io/blog/debug-nodejs
https://marketplace.visualstudio.com/items?itemName=ms-vscode.live-server#running-the-extension
This was added recently (see microsoft/vscode#109276).
1. Open the palette (Ctrl + Shift + P) 2. Select "Simple Browser: Preview" 3. Enter web address
_______________________
const {By, Key, Builder} = require("selenium-webdriver");
const assert = require("assert")
require("chromedriver");
async function test_case(){
let driver = await new Builder().forBrowser("chrome").build();
await driver.get("sie");
await driver.findElement(By.name('password')).clear();
await driver.findElement(By.name('identification')).clear();
await driver.findElement(By.name('identification')).sendKeys("bill");
await driver.findElement(By.name('password')).sendKeys("password", Key.RETURN);
setTimeout(()=>{
driver.findElement(By.className("req_lan_license")).click();
},5000)
defmodule AppWeb.PageLive do
use AppWeb, :live_view
alias App.Testbeds
def mount(_params, _session, socket)do
{:ok, assign(socket, testbeds: Testbeds.list_testbeds())}
end
def button_event("invoke_editing") do
%JS{}
|> JS.remove_class("active", to: "#editing_button")
|> JS.add_class("hidden", to: "#editing_button")
|> JS.remove_class("hidden", to: "#preview_button")
|> JS.add_class("active", to: "#preview_button")
|> JS.remove_class("active", to: "#preview")
|> JS.add_class("hidden", to: "#preview")
|> JS.remove_class("hidden", to: "#editing")
|> JS.add_class("active", to: "#editing")
end
def button_event("invoke_preview") do
%JS{}
|> JS.remove_class("active", to: "#preview_button")
|> JS.add_class("hidden", to: "#preview_button")
|> JS.remove_class("hidden", to: "#editing_button")
|> JS.add_class("active", to: "#editing_button")
|> JS.remove_class("active", to: "#editing")
|> JS.add_class("hidden", to: "#editing")
|> JS.remove_class("hidden", to: "#preview")
|> JS.add_class("active", to: "#preview")
end
def handle_event("send", params, socket) do
hide_modal("notes-modal-0")
{:noreply, socket}
end
def render(assigns) do
~H"""
<%= for testbed <- @testbeds do %>
<.modal id={"notes-modal-#{testbed.id}"}>
<.button phx-click={button_event("invoke_editing")} id="editing_button" class="hidden">
Preview
</.button>
<.button phx-click={button_event("invoke_preview")} id="preview_button">
Edit Me
</.button>
<div id="editing"> PREVIEWING THE WORK</div>
<form phx-submit = "send" id="preview" class="hidden">
<textarea value = {testbed.note}></textarea>
<.button type="submit" phx-click={hide_modal("notes-modal-#{testbed.id}")}>SAVE WORK</.button>
</form>
</.modal>
<%= if testbed.note do %>
<div phx-click={show_modal("notes-modal-#{testbed.id}")}>READ ME ...<%= testbed.note %></div>
<% end %>
<% end %>
"""
end
end
import {HfInference} from "@huggingface/inference";
import dotenv from "dotenv";
dotenv.config()
const HF_ACCESS_TOKEN = process.env.HF_ACCESS_TOKEN
const hf = new HfInference(HF_ACCESS_TOKEN);
/*______Uncomment for this example______________________
const model = "nlpconnect/vit-gpt2-image-captioning";
const imageURL = "https://i.imgur.com/lTvb7Et.png";
const response = await fetch(imageURL);
const imageBlob = await response.blob();
const result = await hf.imageToText({
data: imageBlob,
model: model,
});
_______________________________________________________*/
/*________Another example ___________________*/
const result = await hf.summarization({
model: 'facebook/bart-large-cnn',
inputs: "The role of a dumb man is to get smarter oogy bookie boo",
parameters:{
max_length: 100
}
});
console.log(result);
________________________________________
https://fly.io/phoenix-files/sdeb-toggling-element/ https://www.youtube.com/watch?v=vBgZvQapqhs
https://blog.testdouble.com/posts/2022-11-28-how-to-use-javascript-with-phoenix-liveview/
https://hexdocs.pm/phoenix_live_view/0.19.3/Phoenix.Component.html _______________________________________ https://www.smashingmagazine.com/2011/10/quick-look-math-animations-javascript/
- Depth first traversal
- Breadth first traversal
https://dev.to/codesphere/10-algorithms-every-developer-should-learn-3lnm
https://www.youtube.com/watch?v=fPz40W9mfCg
__________________________________
defmodule AppWeb.GroupLive do
use AppWeb, :live_view
alias App.TestBeds
alias App.Groups
def mount(_params, _session, socket) do
{:ok, assign(socket, testbeds: TestBeds.list_testbeds(), groups: Groups.list_groups())}
end
def handle_event("select-group", params, socket) do
IO.inspect params
{:noreply, socket}
end
def render(assigns) do
~H"""
<div class="grid grid-columns-2 grid-flow-col gap-4">
<%= for testbed <- @testbeds do %>
<div class="column-1">
<div class="name"><%= testbed.name %></div>
</div>
<div class="column-2">
<form phx-change="select-group" >
<input type="hidden" name="testbed_id" value={testbed.id}/>
<select id="group-selection" name="group-id" >
<%= for group <- @groups do %>
<option value= {"#{group.id}"} ><%=group.name %></option>
<% end %>
</select>
</form>
</div>
<% end %>
</div>
"""
end
end
____________________________
defmodule AppWeb.GroupLive do
use AppWeb, :live_view
alias App.TestBeds
alias App.Groups
def mount(_params, _session, socket) do
{:ok, assign(socket, testbeds: TestBeds.list_testbeds(), groups: Groups.list_groups(), messages: [])}
end
def handle_event("select-group", params, socket) do
group = App.Groups.get_group!(params["group-id"])
# IO.inspect group
testbed = App.TestBeds.get_test_bed!(params["testbed_id"])
App.TestBeds.update_test_bed(testbed, %{group_id: group.id})
{:noreply, socket}
end
def updated_groups(testbed_id) do
groups = App.Groups.list_groups
tail = Enum.filter(groups, fn(item)-> item.id !== testbed_id end)
head = Enum.filter(groups, fn(item)-> item.id == testbed_id end)
|>Enum.at(0)
[head | tail]
end
# def handle_event("groups", params, socket)do
# selected_testbed = %{name: "my testbed name", id: 3}
# groups = App.Groups.list_groups
# tail = Enum.filter(groups, fn(item)-> item.id !== selected_testbed.id end)
# head = Enum.filter(groups, fn(item)-> item.id == selected_testbed.id end)
# |>Enum.at(0)
# # result = Enum.filter([1, 2, 3], fn x -> x.id !== 1 end)
# IO.inspect [head | tail]
# #
# {:noreply, socket, assigns.message:}
# end
def render(assigns) do
~H"""
<%!-- <div phx-click = "groups">CLICK ME</div> --%>
<%= for testbed <- @testbeds do %>
<div class="grid grid-cols-4 gap-4 p-8 rounded shadow bg-white">
<h2> Name</h2>
<div class="name"><%= testbed.name %> </div>
<h2> Group</h2>
<div>
<form phx-change="select-group" name="testbed_id" >
<input type="hidden" name="testbed_id" value={testbed.id}/>
<select id="group-selection" name="group-id" class="">
<%= for group <- updated_groups(testbed.id) do %>
<option value= {"#{group.id}"} ><%=group.name %></option>
<!-- Write code to reorder with testbed.group_id matching group_id -->
<% end %>
</select>
</form>
</div>
</div>
<% end %>
"""
end
end
_____________________________________
defmodule AppWeb.PageLive do
use AppWeb, :live_view
alias App.Testbeds
alias App.Groups
def mount(_params, _session, socket) do
{:ok, assign(socket, testbeds: Testbeds.list_testbeds(), groups: Groups.list_groups())}
end
def handle_event("select-group", params, socket) do
group = App.Groups.get_group!(params["group-id"])
testbed = App.Testbeds.get_testbed!(params["testbed_id"])
result = App.Testbeds.update_testbed(testbed, %{group_id: group.id})
IO.inspect result
{:noreply, socket}
end
def updated_groups(testbed_id) do
testbed = App.Testbeds.get_testbed!(testbed_id)
groups = App.Groups.list_groups
tail = Enum.filter(groups, fn(item)-> item.id !== testbed.group_id end)
head = Enum.filter(groups, fn(item)-> item.id == testbed.group_id end)
|>Enum.at(0)
[head | tail]
end
def render(assigns) do
~H"""
<%= for testbed <- @testbeds do %>
<div class="grid grid-cols-4 gap-4 p-8 rounded shadow bg-white">
<h2> Name</h2>
<div class="name"><%= testbed.name %> </div>
<h2> Group</h2>
<div>
<form phx-change="select-group" name="testbed_id" >
<input type="hidden" name="testbed_id" value={testbed.id}/>
<select id="group-selection" name="group-id" class="">
<%= for group <- updated_groups(testbed.id) do %>
<%!-- <option value= {"#{group.id}"}><%=group.name %></option> --%>
<!-- Write code to reorder with testbed.group_id matching group_id -->
<%= if group !== nil do %>
<option value= {"#{group.id}"}><%=group.name %></option>
<% end %>
<% end %>
</select>
</form>
</div>
</div>
<% end %>
"""
end
end
has_many :testbeds, TestBed, on_delete: :restrict # Has many
https://elixirforum.com/t/ecto-has-many-on-delete-protect/49550
https://hexdocs.pm/ecto/Ecto.Changeset.html#unique_constraint/3
____________________________________________________________________
How to Create a Mix Project and Use It in Another Mix Project
To create and use a **Mix project** inside another **Mix project** in Elixir, follow the steps below:
1. Create a New Mix Project
First, you need to create a new Mix project. Let's call this project `library_project`. Run the following command:
mix new library_project --module LibraryProject
This will create a new Elixir Mix project named `library_project`. The `--module` flag specifies the name of the module that will be created (in this case `LibraryProject`).
The directory structure of `library_project` will look like this:
library_project/
|_ lib/
|_ library_project.ex
|_ mix.exs
You can then add some basic functionality inside the `library_project/lib/library_project.ex` file:
defmodule LibraryProject do
def hello do
IO.puts "Hello from LibraryProject!"
end
end
Now, `library_project` is ready to be used by another Mix project.
2. Create a Second Mix Project
Next, create the second Mix project where you want to use `library_project`. For example:
mix new app_project --module AppProject
This will create another Elixir project called `app_project`.
The directory structure for this project will look like:
app_project/
|_ lib/
|_ app_project.ex
|_ mix.exs
3. Add `library_project` as a Dependency in `app_project`
Now, you want to make `library_project` available in `app_project`. There are two main ways to do this:
Option 1: Local Dependency (via `path`)
If `library_project` is a local project (i.e., it's stored in a different directory), you can add it to the `mix.exs` of `app_project` as a local dependency by specifying the path to `library_project`.
Edit the `mix.exs` of `app_project`:
defmodule AppProject.MixProject do
use Mix.Project
def project do
[
app: :app_project,
version: "0.1.0",
elixir: "~> 1.14",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
defp deps do
[
{:library_project, path: "../library_project"} # Add the local dependency
]
end
end
Here, `{:library_project, path: "../library_project"}` tells Mix to use the local `library_project` located in the `../library_project` directory (relative to `app_project`).
After updating the `mix.exs`, run the following command:
cd app_project mix deps.get
This will fetch the dependency and compile `library_project`.
Option 2: Publish and Fetch as a Hex Package
If you want to use `library_project` as a package in another project (for example, by publishing it to **Hex.pm**, Elixir’s package manager), you need to publish it to Hex and then add it as a dependency.
1. Publish `library_project` to Hex (if it's public or for your private use). 2. Add `library_project` to your `app_project`’s `mix.exs` dependencies:
defp deps do
[
{:library_project, "~> 0.1.0"} # Specify the published version from Hex
]
end
Run `mix deps.get` to fetch the package from Hex.
4. Use `library_project` in `app_project`
Now, you can use the functionality from `library_project` inside your `app_project`. For example, in `app_project/lib/app_project.ex`, you can call the function from `library_project`:
defmodule AppProject do
def start do
IO.puts "Starting app_project!"
LibraryProject.hello() # Call the function from the library_project
end
end
5. Run the App
Finally, you can run your `app_project`:
cd app_project mix run -e "AppProject.start()"
This will print:
Starting app_project! Hello from LibraryProject!
Recap of the Process
1. **Create the `library_project`**: Use `mix new` to generate a library project. 2. **Create the `app_project`**: Create the second project where you will use the library. 3. **Add the `library_project` as a dependency**:
- Use the `path` option to point to the local project or publish it to Hex for public access.
4. **Use the `library_project` in the `app_project`**: Call functions or use modules from the library inside the second project. 5. **Run the app**: Use `mix run` to execute the project.
This way, you can reuse code from one Mix project in another by adding it as a dependency.
z
Overloaded functions are extremely common in Elixir applications. They are regularly used for recursive functions and for changing behavior based on configuration.