Pokazywanie postów oznaczonych etykietą JavaScript. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą JavaScript. Pokaż wszystkie posty

środa, 23 października 2024

Tworzenie aplikacji Next.js, Postgres, Prisma i deploy na platformie Vercel

Aparte na opisie z How to Build a Fullstack App with Next.js, Prisma, and Vercel Postgres

Tworzę aplikację Next.js o nazwie next-test

npx create-next-app@latest next-test

Zatwierdzam wszystkie parametry domyślne
Uzupełnić .gitignore i można sobie uruchomić aplikację, aby sprawdzić czy wszystko działa

npm run dev

Teraz należy przesłać repozytorium do Github
git push origin main 

Utworzyć aplikację na Vercel o nnazwie, najlepiej takiej jak utworzona, czyli w naszym przypadku next-test. Utworzenie aplikacji na Vercel sprowadza się do nadania jej w Github uprawnienia dla portalu Vercel.

W projekcie Vercel kliknąć zakładkę Storage i utworzyć bazę danych Postgres. Utworzoną bazę przypisać do projektu. Podczas tego procesu powstanie szereg zmiennych środowiskowych opisujących bazę Postgres

Teraz czas na instalację CLI Vercel:

npm i -g vercel@latest

Gdy się wszystko zainstaluje ściągamy utworzone wcześniej zmienne środowiskowe do lokalnego pliku .env, ale przedtem linkujemy się do projektu

Czyli linkowanie:

vercel link

A potem ściągnięcie zmiennych środowiskowych

vercel env pull .env

Warto umieścić zapis .env* w pliku .gitignore

Instalujemy pakiet Prisma:


npm install prisma --save-dev

Tworzymy folder prisma, a w nim plik schema.prisma

mkdir prisma && touch prisma/schema.prisma

W pliku schema.prisma umieszczamy przykładowy kod

// schema.prisma

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url = env("POSTGRES_PRISMA_URL") // uses connection pooling
  directUrl = env("POSTGRES_URL_NON_POOLING") // uses a direct connection
}

model Post {
  id        String     @default(cuid()) @id
  title     String
  content   String?
  published Boolean @default(false)
  author    User?   @relation(fields: [authorId], references: [id])
  authorId  String?
}

model User {
  id            String       @default(cuid()) @id
  name          String?
  email         String?   @unique
  createdAt     DateTime  @default(now()) @map(name: "created_at")
  updatedAt     DateTime  @updatedAt @map(name: "updated_at")
  posts         Post[]
  @@map(name: "users")
}

Zmienne POSTGRES_PRISMA_URL i POSTGRES_URL_NON_POOLING są nazwami wziętymi z pliku .env ściągniętego z Vercel.

Tworzymy tabele w bazie danych, a właściwie wypychamy schemę do bazy danych:

npx prisma db push

Można sobie sprawdzić w panelu bazy danych na Vercel, czy obie tabele zostały poprawnie utworzone.

Teraz można uruchomić Prisma Studio:

npx prisma studio

Podczas uruchomiania Prisma Studio linia komend wskaże linka z portem, z którego można uruchomić Studio w przeglądarce.

Tworzymy rekordy dla User i Post w Prisma Stidio

Instalujemy Prisma CLient:


npm install @prisma/client

Za każdym razem, gdy plik schema.prisma zostaje zmieniony należy wykonać generowanie:

npx prisma generate

Teraz tworzymy folder lib, a w nim plik prisma.ts

mkdir lib && touch lib/prisma.ts

Wypełniamy plik prisma.ts

// lib/prisma.ts

import { PrismaClient } from '@prisma/client';

let prisma: PrismaClient;

if (process.env.NODE_ENV === 'production') {
  prisma = new PrismaClient();
} else {
  if (!global.prisma) {
    global.prisma = new PrismaClient();
  }
  prisma = global.prisma;
}

export default prisma;

Może być problem z typem dla zmiennej prisma. Wtedy w katalogu głównym projektu utówrz plik global.d.ts

touch global.d.ts

a w nim ten kod:

// /global.d.ts

import { PrismaClient } from '@prisma/client';

declare global {
  // eslint-disable-next-line no-var
  var prisma: PrismaClient;
}

Teraz w każdym miejscu, gdy potrzebujemy dostępu do bazy danych możemy wykorzystać zmienną prisma importując ją tak:

import prisma from '../lib/prisma';

Przykładowe wykorzystanie

W folderze lib tworzymy plik actions.ts

touch lib/actions.ts

Do pliku actions.ts dodajemy funkcję getPosts()

// lib/actions.ts

import prisma from './prisma';

export async function getPosts(isPublished: boolean = true) {
  return prisma.post.findMany({
    where: { published: isPublished },
    include: {
      author: {
        select: { name: true },
      },
    },
  });
}

Tworzymy route posts

mkdir app/posts && touch app/posts/page.tsx

Do pliku posts/page.tsx dodajemy następujący kod, aby zobaczyć nnaniesione wcześniej w Prisma Studio posty:

// posts/page.tsx

import { getPosts } from "@/lib/actions";
import { Post } from "@prisma/client";

export default async function Posts() {
  const posts: Post[] = await getPosts(false);

  return (
    {posts.map((post) => {
      return <div key="{post.id}">{post.title}<div>;
    })}
  );
}

To już prawie wszystko.

Prawie, gdyż w tym stanie deploy na Vercel może pokazać błędy. Jest to spowodawane tym, żę Prisma wymaga uruchomienia npx prism generate po każdej zmianie.

Dodajemy więc "postinstall": "prisma generate" do sekcji "scripts" w pliku package.json

Cała więc sekcja "scripts" powinna wyglądać mniej więcej tak:


"scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "postinstall": "prisma generate"
  }

czwartek, 1 grudnia 2011

Dynamiczne dodawanie opcji do kontrolki "select"

Wydawałoby się, że jest to prosta i jakże częsta akcja twórców stron i aplikacji internetowych. Jednak nie udało mi się znaleźć żadnego(sic!) rozwiązania, które byłoby zgodne z duchem Rails. Jak nie ma, to trzeba napisać. Siadłem i popełniłem kod jak poniżej.

Założenia są proste (podam w składni Rails) jest jakaś lista select, na przykład:
<%= select_tag :event_container %>
i chciałbym do tej listy dorzucać pracowników z listy
<%= select_tag(:employee_id,
                options_for_select(Employee.all.map { |employee| [employee.full_name, employee.id] }) %>
Chcę sobie kliknąć na listę z pracownikami, a po kliknięciu zaznaczony pracownik ma "przeskoczyć" z jednej listy na drugą. "Przeskoczyć" to znaczy zniknąć z jednej listy, a pojawić się na drugiej i vice versa.
Do widoku wrzucam więc obserwatory:
<%= observe_field "employee_id",
                   :url => {:action => :move_employee_name},
                   :with => "employee_id" %>
<%= observe_field "event_container",
                   :url => {:action => :remove_employee_from_event},
                   :with => "event_container" %>
i tworzę dla nich pliki RJS odpowiednio: move_employee_name.rjs i remove_employee_from_event.rjs. Muszą one być skorelowane z metodami o takich samych nazwach w kontrolerze! Nie zapomnieć!
Wpisy w move_employee_name.rjs dotyczą oczywiście kontrolek select:
page[:event_container].addNewOption @employee.full_name, @employee.id
page[:employee_id].removeSelectedOption
Wpisy w remove_employee_from_event.rjs są analogiczne
page[:employee_id].addNewOption @employee.full_name, @employee.id
page[:event_container].removeSelectedOption

I tu pytanie za 12 punktów: skąd RJS zna te dziwne metody? Mowa oczywiście o ".addNewOption" i ".removeSelectedOption". I od razu odpowiedź: wpisałem je w /public/javascripts/application.js w postaci:
/* Dodawanie metod do szablonów RJS. */
Element.addMethods({
 /* Dodawanie nowej opcji do kontrolki select. W tym przypadku jest to "element". */
 addNewOption: function(element, option, id) {
  element.options.add(new Option(option, id));
  return element;
 },
 /* Usuwanie zaznaczonej opcji w kontrolce select. Bez parametrów, gdyż funkcja leci po wszystkich. */
 removeSelectedOption: function(element) {
  var i;
  for (i = element.length - 1; i >= 0; i--) {
   if (element.options[i].selected) {
    element.remove(i);
    return;
   }
  }
 }
});
Tam jest właśnie cała magia :-)

Sposób opisany tu przeze mnie jest jak najbardziej zgodny z ideą Rails. Daje się łatwo utrzymać w kodzie aplikacji i jest czytelny. Nic tylko brać!

wtorek, 10 maja 2011

System do próbowania html/css/javascript

Na dodatek wspierający frameworki Mootools, jQuery, Prototype, YUI, Dojo, ExtJS i kilka innych.
Wskocz na http://jsfiddle.net/

Piszesz po prostu kod html okraszasz go css'em i uaktywniasz Javascriptem w dowolnej konfiguracji. Efekt po naciśnięciu klawisza "Run".


poniedziałek, 9 maja 2011

Pozycja myszy w JavaScript i dynamiczne ustawianie pojemnika

Czasami zachodzi potrzeba aby pojemnik z wygenerowaną zawartością (na przykład Ajaxową) pojawił się w okolicach kursora myszy. Można to wykonać na różny sposób, ale że dużo piszę Ruby on Rails, które używa biblioteki Prototype, to opiszę na tej właśnie drodze.
Aby zapoznać się z działaniem mechanizmów można wypróbować ten kod:
<script type="text/javascript" >
  function getcords(e){
    mouseX = Event.pointerX(e);
    mouseY = Event.pointerY(e);
    //dla celów testowych
    $('debug').innerHTML = 'mouseX:' + mouseX + '-- mouseY:' + mouseY;
  }
  Event.observe(document, 'mousemove', getcords);
</script>
<div id="debug">
  tu za chwilę pojawią się koordynaty kursora myszy
</div> 

Kluczowym jest tu obserwator (albo donosiciel) zdarzeń Event.observe, który w tym przypadku obserwuje cały dokument i śledząc położenie myszy "donosi" do kontenera <div id="debug">.
Jak wcześniej wspomniałem, opisuję to jedynie aby pokazać mechanizm. Nam chodzi o coś innego.
Potrzeba była taka: po prawej stronie ekranu jest lista pytań skróconych $('question_list'). Zajmuje ona całą wysokość ekranu. Klikniesz sobie na pytanie skrócone, a Rails przeszuka bazę i dostarczy pełną treść pytania. Treść tę umieści w kontenerze $('question_long'). Kontener ten wisi sobie spokojnie w jednym miejscu strony, a nam chodziło o to, aby pokazywał się na wysokości klikniętego pytanka na liście, i aby wszystko było pod ręką i przed oczami.
No to trzask:
<script type="text/javascript">
  function getcords(e) {
    mouseY = Event.pointerY(e);
    $('question_long').setStyle('top: ' + mouseY + 'px;');
  }
  Event.observe($('question_list'), 'click', getcords);
</script>
czary mary i działa!

Krótki opis:

Linijka 4:
Element.setStyle nadaje styl CSS wybranemu elementowi na stronie. W naszym przypadku kontenerowi z wygenerowaną pełną treścią pytania. Ustawiamy mu pozycję patrząc od góry strony. Uwaga nie zapomnij mu nadać odpowiedniego pozycjonowania:
#question_long {
  position: fixed;
  right: 200px;
  width: 300px;
  /* tra ta ta, kolory, zaokrąglenia rogów itp. */
}

Linijka 6:
Obserwujemy teraz nie cały dokument, a jedynie listę z pytaniami skróconymi. Nie interesują nas też oczywiście wszystkie ruchy myszy w zakresie tej listy, ale konkretne kliknięcia.

Oczywiście można to wszystko zrobić bez Prototype, ale skoro już jest... to dlaczego nie użyć?

niedziela, 3 kwietnia 2011

Jak w JavaScript przenieść się na inną stronę

Ciągle zapominam tę komendę i tę składnię, postanowiłem więc to sobie zapisać:
window.location.href = 'http://piszemyprogramy.pl';
JavaScript napotykając taką frazę przeniesie nas na naszą ulubioną stronę http://piszemyprogramy.pl

Fragmentu adresu

Często się zdarza, że potrzebuję z adresu wymontować jego fragment. Zamiast się męczyć z wyrażeniami regularnymi można to zrobić tak:
window.location.host : dostaniesz sub.domain.com:8080 lub sub.domain.com:80
window.location.hostname : dostaniesz sub.domain.com
window.location.protocol : dostaniesz http:
window.location.port : dostaniesz 8080 lub 80
window.location.origin : dostaniesz http://sub.domain.com
window.location.pathname : dostaniesz (na przykład) orders/index


środa, 23 marca 2011

Jak w jQuery wywołać funkcję z parametrami

JQuery w bardzo wielu miejscach używa funkcji bezimiennej jako parametru.
Na przykład jeśli chcemy zareagować na zmianę w polu "#name":
jQuery("#name").change(function(){
  //tu jakieś operacje
});
Wszystko fajnie do momentu, gdy te wewnętrzne funkcje nie zaczynają się rozrastać i ciągnąć za sobą nowe funkcje. Kod staje się całkowicie nieczytelny i bałagan z każdą chwilą się pogłębia.
Częściowym rozwiązaniem jest osadzanie funkcji nazwanych jako parametru.
W poprzednim przykładzie funkcję bezimienną moglibyśmy zastąpić nazwaną, jak choćby:
function makeChanges() {
  //tu kod z funkcji nienazwanej
}
którą osadzamy w poprzednim przykładzie:
jQuery("#name").change(makeChanges);
Zwróć uwagę na brak nawiasów!

Taaa... wygląda nieźle.  Wygląda nieźle, ale tylko do momentu, w którym musimy użyć funkcji makeChanges() w jakimś innym miejscu kodu. Po to przecież ją wyekstrahowaliśmy (pamiętasz zasadę DRY?). Potrzebny wtedy jakiś parametr, a najczęściej dwa... albo i trzy.
Niestety coś takiego w sposób prosty nie zadziała:
jQuery("#name").change(makeChanges(paramOne, paramTwo));
Aby zadziałało funkcja makeChanges() musi zwrócić funkcję gdyż tego wymaga jQuery. Zróbmy więc tak:
function makeChanges(paramOne, paramTwo) {
  return function() { 
    // tu dopiero kod z funkcji nienazwanej
    // z wykorzystaniem parametru paramOne
    // i parametru paramTwo
  } 
}
Teraz działa!

Miłej zabawy :-)

środa, 2 marca 2011

JavaScript - Jak ustawić kursor w pierwszym polu formularza na stronie www - focus()

Można to oczywiście sprytniej zrobic przy pomocy jQuery, ale to rozwiązanie ma tę zaletę, że nie potrzebuje żadnych dodatkowych starań. Czysty kod i już:


function formfocus() {
    if(document.forms.length > 0) {
        var formElements = ["text", "checkbox", "radio", "select-one", "select-multiple", "textarea"];
        var form = document.forms[document.forms.length-1];
        for (var j = 0; j < form.elements.length; j++) { 
            var field = form.elements[j]; 
            for(var x = 0; x < formElements.length; x++) { 
            if (field.getAttribute("type") == formElements[x]) { 
                field.focus(); 
                return false; 
            } 
        } 
    } 
} 
 

A w definicji body strony należy wstawić: onload="formfocus();

jQuery - autocomplete wyzwalanie akcji

Ciężko tu zrozumieć co miałem na myśli osobom nie z branży, albo i nawet z branży.

A problem jest taki, że autocomplete z frameworka jQuery w momencie wybrania w polu tekstowym właściwej opcji nie robi nic. A czasami fajnie by było aby po wybraniu dokonał akcji takiej jakby był wciśnięty klawiszy "submit" formularza.

Można zrobić to tak:

$('input#query').autocomplete('yoururl',{
    .............
    your options
    .............
}).result(function(event, item, formatted) {
    $(this).parents().find('form:first').submit();
});

piątek, 25 lutego 2011

Jak w jQuery dodawać opcje do listy.

Czasami chcemy dynamicznie dodać opcje np.
<option>Dodatkowa opcja</option>


do wysuwanej listy <select>

Można zrobić to tak:
$.each(selectValues, function(key, value){
    $('#mySelect').
    append($("").
    attr("value",key).
    text(value));
});

albo lepiej:
var thisValue = 12;
var thisText = 'December';
var thisOpt = document.createElement('option');
thisOpt.value = thisValue;
thisOpt.appendChild(document.createTextNode(thisText));
$("#name_select_field").append(thisOpt);

wtorek, 22 lutego 2011

Automatyczne rozbudowywanie kodu poprzez JavaScript

Pisząc kod stron, a szczególnie formularzy często pojawiają się standardowe elementy strony np.
<input type="submit" value="Zatwierdź"/>
Nic wielkiego, ale grafik sobie zażyczył takiego:
<div class="bt_submit">
        <img src="images/bckg_submit_left.png" alt=""/>
        <span>Zatwierdź</span>
        <img src="images/bckg_submit_right.png" alt=""/>
</div>
Nie ma rady. Trzeba tak pisać. Po trzecim razie miałem dość. Bo gdzie jest zasada DRY (Dont Repeat Yurself?).
Wymyśliłem tak: tam gdzie będzie potrzebny przycisk "Zatwierdź" w kodzie strony napiszę:
<div class="bt">Zatwierdź</div>
a do tego napisałem trochę prostego kodu w JavaScripcie.
Korzystam z frameworka jQuery więc by to wyglądało tak:
jQuery(document).ready(function() {
    var bt=jQuery('div.bt');
    makeBtSubmit(bt);
});

function makeBtSubmit(bt) {
    var btText=bt.html();
    var initialBtClassName='bt'; //umowna
    var targetBtClassName='bt_submit'; //umowna, ale posiadająca swój CSS
    bt.removeClass(initialBtClassName);
    bt.addClass(targetBtClassName);
    var imageLeft='<img src="images/bckg_submit_left.png" alt=""/>';
    var imageRight='<img src="images/bckg_submit_right.png" alt=""/>';
    var spanInside='<span>'+btText+'</span>';
    var htmlInside=imageLeft+spanInside+imageRight;
    bt.html(htmlInside);
}
Teraz więc wszędzie gdzie wystapi mój kod
<div class="bt">Zatwierdź</div>
zostanie automatycznie zamieniony na kod wymagany przez grafika.