<![CDATA[Marko Ilić]]>https://ilic.ninja/http://ilic.ninja/favicon.pngMarko Ilićhttps://ilic.ninja/Ghost 2.19Thu, 21 Nov 2019 05:54:32 GMT60<![CDATA[GitGud: Prepisivanje grane drugom granom]]>https://ilic.ninja/gitgud-prepisivanje-grane-drugom-granom/5d6e2dd95b00fa411f85de5dTue, 01 Oct 2019 20:30:06 GMTRecimo da imamo slučaj gde je jedna grana toliko zastarela da nema smisla da se radi "merge". Ovo je realan primer koji može da se desi ako se recimo na master grani nalazi V1 verzija naše aplikacije a na drugoj grani V2 verzija. Kada završimo V2 verziju logično je da ona bude aktivna i nalazi se na master grani.

Kako ovo eleganto uraditi pomoću gita?

Kao i uvek, tajna se krije duboko u dokumentaciji. Ako zagledamo malo dublje koje su opcije git merge komande, možemo da vidimo da ima opcija za biranje strategije.

Ako pogledamo strategiju ours videćemo da je to ono što nam treba.

This resolves any number of heads, but the resulting tree of the merge is always that of the current branch head, effectively ignoring all changes from all other branches. It is meant to be used to supersede old development history of side branches.

Što znači da će se sve izmene sa naše grane uzeti "na slepo" i preklopiće izmene sa osnovne grane. Pa da vidimo to u praksi:

  1. Prvo će da odemo na granu koju želimo da postane naš master
git checkout v2

2. Zatim će da u v2 "merge"ujemo master ali će da zadržimo sve naše izmene.

git merge -s ours master

Ako ne želimo da imamo "commit", možemo da prosledimo opciju --no-commit.
git merge -s ours --no-commit master

3. Vratimo se na master granu

git checkout master

4. "Merge"ujemo v2

git merge v2

Ovim smo preklopili master granu v2 granom. Istorija master grane je zadržana.

Zbog loše dokumentacije ili lošeg formatiranja često dolazi do greške da developeri pomešaju git merge -s ours xyz sa git merge -X ours xyz. Ove dve stvari uopšte nisu iste. Ako malo bolje pogledamo dokumentaciju, možemo da vidimo da se prvo ours nalazi ispod recursive opcije i ta je to pod-opcija, iz tog razloga i ima -X

]]>
<![CDATA[Frontend na dijeti pomoću budžeta]]>https://ilic.ninja/frontend-na-dijeti-pomocu-budzeta/5d2486925b00fa411f85d691Sat, 17 Aug 2019 15:37:00 GMTJedna od bitnijih stvari danas je brzina učitavanja stranice. Google je pokazao da ako se stranica učitava duže od 3s čak 53% ljudi neće imati strpljenja i odustaće i pre nego što se stranica učita. Iz tog razloga je važno paziti na performanse tokom izrade sajta. Osim Google-a, istraživanje su radile i druge velike kompanije kao što su Amazon, Yahoo, Walmart i Ebay. Sve su došle do istog zaključka, a to je da su performanse (brzina) mnogo bitne. Evo i statistike do koje su oni došli.

Glavne stavke koje utiču na učitavanje stranice su resursi i javaskripta koja se izvršava. Ove probleme možemo vrlo lako identifikovati na razlicite načine, koristeći interne ili eketerne alate (možda će biti neki post o ovome kasnije). Ali ovaj posao postaje dosta teži kada se radi CD (Continuous Development), zato što je potrebno performanse testirati posle svake izmene. Dodatni problem predstavlja rad u timu, ko testira i koje su granice da se je nesto dobro?

Ovaj problem je poznat i više puta obrađen, a to je da postavimo budžet. Isto kao i kod izrade projekta, gde postoji finansijski budžet ili vremenski budžet (rok), tako treba da postoji budžet za performanse.

Šta je budžet performansi?

Definicija je vrlo slobodna i otvorena, ali možemo da kažemo da je to ograničenje za stranice koje tim ne sme da prekorači. Namerno nisam definisao na šta se odnosi ograničenje, zato što to zavisi od tima. Ograničenje može da se postavi na broj resursa, veličinu stranice, ukupnu veličina svih slika,...

Šta meriti?

Kada sam iznad definisao šta je budžet performansi, pomenuo sam da možemo da ograničimo različite stvari. Ograničenja možemo da podelimo u tri kategorije:

Vremenski događaji

To su događaji koji se okidaju posle određenog vremenskog perioda prilikom učitavanja stranice. Takvi događaji su Time-To-Interactive, First Contentful Paint, domContentLoaded,...

Kada pratimo vremenske događaje bitno je da pratimo više od jednog događaja. Zato što se često dešava da se jedna stranica učitava za 5 sekundi ali se veći deo sadržaja učita za manje od jedne sekunde dok se druga stranica učitava za dve sekunde a sadržaj za jednu i po sekundu. Iako se prva stanica učitava tri sekunde duže, sam sadržaj se brže učitao.

Zato je bitno da pratimo više vremenskih događaja i da ih uparimo sa drugim metrikama.

Kvantitativno ograničenje

Kao i što samo ime kaže, ovo su sirove brojke koje određuju, na primer, koliko zahteva ima ili kolika je veličina stranice. Ove brojke ne preslikavaju realno performanse, zato sto dve stranice mogu da budu iste veličine a da se jedna dosta brže učitava od druge.

Svakako, ova ograničenja igraju svoju ulogu i mogu biti korisna ako se upare sa drugim metrikama. Za razliku od drugih ograničenja, ovo je dosta lakše razumeti i odrediti. Znamo da ako naša stranica zauzima x i mi dodamo skriptu koja zauzima y da će naša stranica zauzimati x+y.

Ja lično koristim ovo ograničenje kao neku vrstu fizičkog ograničenja, u smislu da cela stranica treba da se učita za x sekundi na 3G mreži. Na primer:

Željeno vreme učitavanja stranice: 2 sekunde
Konekcija klijenta: 3G (1.6 Mbps)
Maksimalna veličina stranice:1 400 Kb

Kako stalno ne bismo računali koliko stranica može da teži, postoji kalkulator koji će da uradi svu matematiku za nas. Kao bonus, kalkulator nam pruža mogućnost da rasporedimo resurse po tipu.

Raspored budžeta po tipu resursa

Eksterne mere

U ovom slučaju kao mera ograničenja se koriste eksterni alati, kao što su Lighthouse ili Webpagetest. Obično se koristi jedna ili više brojki iz razultata kao ograničenje.

Iako je ovo ograničenje dosta bolje od kvantitativnog ograničenja, zato što pruža celokupnu sliku stranice, ipak nije idealno rešenje. Postoji odličan članak koji pokazuje da je moguće da se napravi sajt koji ima ocenu 100 u eksternim alatima, a da zapravo sajt nije moguće koristiti.

Šta je bitno?

Prošli smo kroz različite vrste ograničenja ali kada imamo više opcija uvek se postalvja pitanje koje ograničenje je pravo ograničenje? Odgovor na ovo pitanje je dosta subjektivno, ali mislim da svi objektivno možemo da se složimo da je idealno imati kombinaciju ograničenja. Zato što nijedno ograničenje neće da nam da potpunu sliku, ali ako iskoristimo kombinaciju ograničenja možemo sigurno pokriti mnogo više slučajeva.

Često akteri, dizajneri i ljudi bez inžinjerske pozadine nisu svesni da njihove odluke utiču loše na performanse. Ovo je u neku ruku razumljivo, naša uloga je da objasnimo projekt menadžerima, dizajnerima i akterima kako izmene utiču na korisničko iskustvo.

Iz tog razloga je bitno da performans budžet postane deo projekta i nađe se na početku a ne na kraju kao opcija.

Uključite budžete u proces izgradnje

Ja lično imam jedno bitnije pitanje, ustvari inspiraciju za ovaj članak, a to je kako da integrišemo ovo unutar tima? U uvodu sam pomenuo isto ovo, a to je da je lako izmeriti i naći probleme ali kako integrisati ceo ovaj proces u tim gde radi više od jednog čoveka sa konstantnim izmenama (CI/CD).

Kroz ovaj članak ću pokušati da pokrijem kako da integrišemo budžet u sklopu git procesa, tako da pri kreiranju svakog PR-a dobijemo rezultate za performanse. Naravno, da bismo ovo uspeli potreban nam je CI, konkretno za ovaj primer ću korististi CircleCI ali se isto odnosi i na druge CI-ove.

Aplikacija koju testiramo

Aplikacija koju testiramo je SPA aplikacija koja je napravljena kao deo prezentacije. Homemau je aplikacija koja predstavlja dom za mačke i gde drugi ljudi mogu potražiti mačku koja će biti njihov novi kućni ljubimac. Aplikacija nema nikakvu konkretnu funkcionalnost već služi kao primer.

Ciljevi testiranja

  • Prilikom pravljenja PR-a novi kôd treba testirati svaki put.
  • Aplikacija mora da ima Lighthouse perfomance ocenu manju od 90.
  • Aplikacija mora da ima manji bundle od 200kb.
  • Pokrenuti sve testove više puta i prikazati prosek.
  • Ispisati rezultate izveštaja unutar PR-a. Takođe, korisno je ako možemo da ostavimo link do HTML izveštaja.
  • Ako ne ispoštujemo budžet PR treba biti odbijen.

Postavljanje budžeta

Kako bismo ispoštovali sve ono o čemu smo pričali, moramo da odredimo budžet. U idealnom svetu ovo se određuje na početku projekta kad i finansijski budžet. Recimo da smo se složili koji je naš budžet, šta dalje?

Ovo je na nama. Možemo da napravimo novi fajl, i moja praksa je da se on nalazi unutar package.json.  Smatram da sve što se tiče projekta treba da se nalazi tu i dosta je lakše da prolazimo kroz jedan konfiguaracioni fajl nego kroz više manjih fajlova.

"budget": {
    "lighthouse": {
        "performance": 90,
        "accessibility": 80,
        "best-practices": 80,
      	"seo": 80
    },
}

Proces testiranja

Kako bismo se malo bolje upoznali sa CircleCI-em i terminologijom, proćiću korak po korak kroz proces testiranja.

graph LR
	A[Instaliraj biblioteke]-->B[Build and deploy]
	B-->C[Pokreni Lighthouse]
	C-->D[Analiziraj i ažuriraj PR]

U CircleCI-u svaki od ovih koraka se zove "job" a sekvenca poslova (kao što mi imamo) je "workflow". Konfiguraciju za CircleCI čuvamo u config.yaml koji treba da stavimo u .circleci folder.

Drugi korak u celom ovom procesu je vrlo bitan i mnogo zavisi od našeg okruženja. Vrlo je bitno da postoji mogućnost da iz našeg CI-a pokrenemo deploy proces, zato što nam je potreban link do poslednje verzije aplikacije.

Korisno je da imamo više okruženja prilikom izrade. Obično je praksa da postoje development, staging i live okruženje. Ovo je možda članak sam za sebe ali je bitno za ovaj proces.

Pokretanje Lighthouse-a

Ono što treba da uradimo jeste da pokrenemo Lighthouse u odnosu na naš development ili staging server. Lighthouse pruza CLI koji možemo da iskoristimo ali potrebno nam je celokupno okruženje. Na svu sreću postoji Docker container kporras07/lighthouse-ci koji mi možemo da iskoristimo kao neku vrstu interfejsa.

 runPerf:
    docker:
      - image: kporras07/lighthouse-ci

    steps:
      - checkout

      - run:
          name: Run lighthouse against staging deployment

          environment:
            DEV_URL: https://marko.ilic.ninja/homemau

          command: |
            lighthouse $TEST_URL \
              --chrome-flags=\"--headless\" \
              --output-path=/home/chrome/reports/anonymous-"$(echo -n $CIRCLE_SHELL_ENV | md5sum | awk '{print $1}')" \
              --output=json \
              --output=html

      - persist_to_workspace:
          root: /home/chrome
          paths:
            - reports

Ovo što smo ovde opisali je samo treći korak našeg procesa.

CircleCI će prvo da preuzme i pokrene Docker image kporras07/lighhouse-ci.

Zatim se izvršava samo jedan korak, kom smo dodelili ime (name) koje kasnije vidimo unutar CircleCI-a. Postavimo promenjivu DEV_URL koju ćemo kasnije da koristimo. Na kraju pokrenemo lighthouse komandu kojoj prosledimo par parametara, kao što su output parametri za tip izveštaja koji želimo.

JSON izveštaj koristimo radi lakšeg upoređivanja sa našim budžetom. Dok je HTML izveštaj radi prikaza i linkovanja u PR.

Opcija persist_to_workspace daje mogućnost da sve iz tog kontejnera ostane dostupno pod /reports direktorijumom unutar workspace-a.

Ako sada pokrenemo CircleCI on će pokrenuti test, generisati izveštaje i sačuvaće ih u /reports direkotorijumu.

Poređenje i postavljanje izveštaja

Nakon generisanja izveštaja neophodno ih je uporediti sa postavljenim budžetom na početku. Zato hajde da napišemo skriptu koja će da radi poređenje i zatim postavi komentar na PR.

Za ovo ne postoji neki "gotov" ili specijalan način, pošto radimo od nule, ovu skriptu moramo sami napisati. Pošto imamo JSON fajlove mislim da je najlakše da skriptu napišemo u NodeJS-u.

Pravimo novi "job" u CI-u:

updatePr:
    docker:
      - image: circleci/node:11.4.0

    steps:
      - checkout
      - restore_cache:
          keys:
            - node-v1-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
      - attach_workspace:
          at: "."
      - store_artifacts:
          path: reports
          destination: reports
      - run:
          name: "Analyze and comment to the PR"
          command: ./.circleci/build-score.js budget.json reports

Pošto nam je potreban Node ja ću iskoristiti najnoviji "container" node:11.4.0. Takođe ono što je vrlo bitno je da imamo pristup izveštajima. Kako smo u prethodnom "job"-u koristili persist_to_workspace sada možemo isti taj folder iskoristiti tako što ćemo ga dodati pomoću attach_workspace komande. Još jedna stvar koja nam je bitna da imamo pristup HTML izveštaju unutar PR-a. Time postižemo da uvek imamo pristup izveštaju i detaljniji pregled. CircleCI pruža artifakte, koji služe za čuvanje podataka nakon što se poslovi završe. Artifakte možemo da koristimo koristeći store_artifacts  komandu.

Kad smo sve to lepo podeseli, vreme je da pokrenemo našu skriptu. Ja ću je čuvati u .cricleci folderu i zvaće se build-score.js.

Ono što sam dodao je da skripta prima dva parametra, putanju do budžeta i putanju do direktorijuma gde se čuvaju izveštaji. Ovo je zato što u slučaju da odlučim da promenim imena ovih datoteka ne moram da menjam skriptu već je skripta ista za svaki projekat.

Pisanje skripte

Ja ću ovde pokriti samo neke osnove kao kako postaviti komentar i uzeti izveštaj. Cela skripta je dostupna na Github-u.

Kako bih lakše postavljao komentar i uzeo link do artifakta koristiću circle-github-bot ovo je konkretno bot za Github koji u pozadini koristi ništa više nego Github API. Tako da je vrlo lako uraditi ovo i za Bitbucket.

#!/usr/local/bin/node

const fs = require('fs');
const path = require('path');
const bot = require('circle-github-bot').create();

const budget = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
const { lighthouse } = budget;

// Ucitaj sve izvestaje

const reportsFolder = process.argv[3];
const reports = {
    json: [],
    html: [],
};

fs.readdirSync(reportsFolder).forEach(file => {
    switch (path.extname(file)) {
        case '.json':
            reports.json.push(JSON.parse(fs.readFileSync(path.join(reportsFolder, file), 'utf8')));
            break;
        case '.html':
            reports.html.push(file);
            break;
    }
});

Sada kad imam pristup svim izveštajima i budžetu koji smo postavili, možemo vrlo lako da manipulišemo podacima, zatim da ih formatiramo i postavimo kao komentar.

Prilikom generisanja linka za artifakte koristi se funkcija artifactLink() ali putanja koju vrati nije tačna zato što vrati punu putanju koja ima unutar sebe /home/circleci/project. Ovo samo treba zameniti praznim stringom.

Evo primera kako izgleda moja funkcija za uzimanje svih HTML izveštaja:


const reportLinks = reports.html.map((filename, i) => {
  return bot.artifactLink(`reports/${filename}`, `Report ${i + 1}`).replace('/home/circleci/project', '');
});

Evo kako izgleda kranji izveštaj:

Komentar na PR-u

Testovi po meri

Na početku sam pričao o različitim metrikama koje možemo da koristimo i vidimo da Lighthouse pokriva veliki deo, ali ipak ima nekih nedostataka. Recimo da hoćemo da ograničimo veličinu našeh izlaznog JS fajla.

Ako zagrebemo malo dublje u Lighthouse dokumentaciju možemo da nađemo način na koji se pišu testovi po meri. Možemo da iskoristimo Lighthouse da nam vrati neke korisne podatke, zatim da uporedimo sa našim budžetom.

Arhitektura Lighthouse-a, Github.

Kako bismo napisali test potrebne su dve komponente:

  • Sakupljač (Gatherer) - skuplja potrebne podatke za test.
  • Test (Audit) - pokreće se i vraća true ili false.

Za početak je potrebno da definišemo podešavanja koje će Lighthouse da uzme. Nazvaćemo fajl weight-audit-config.js

module.exports = {
    // Pokreni nas test uz ostale regularne (default) testove
    extends: "lighthouse:default",
    
    // Testovi koji ce da se pokrenu uz Lighthouse
    // Na osnovu ovog se odredjuje ime fajla.
    audits: ["weight-audit"],
    
    // Napravi novu kategoriju `Velicina JS datoteke`
    categories: {
        "js-weight": {
            title: "Velicina JS datoteke",
            description: "Alooo bato, ajmo malo crossfit za sajt!",
            auditRefs: [{ id: "weight-audit", weight: 1 }]
        }
    }
};

Pošto smo gore nazvali test weight-audit tako isto moramo da nazovemo fajl koji će da pokreće test. Zato pravim fajl weight-audit.js.

Ustvari, pre nego što napravimo taj fajl bitno je da razmislimo gde da stavimo koliki je naš budžet i možda još bitnije ime fajla. Mislim da je najbolje da se ovi podaci nalaze tamo gde i sav budžet budget.json.

"bundleSize": 200,
"bundleName": "app.js"

Potencijalni problem ako bundleName bude ovako definisan je što ako korisitmo neki heš prilikom generisanje fajla to neće da radi. Iz tog razloga ovde možemo da napišemo RegEx i kasnije koristimo test() metodu.

Tako da u našem slučaju taj RegEx može da izgleda ovako js/app.*\\.js

Da se vratimo na weight-audit.js

// Zato sto je `npm link` pravio problem morao sam da navedem celu putanju
const Audit = require("/usr/local/lib/node_modules/lighthouse").Audit;

class WeightAudit extends Audit {
    static get meta() {
        return {
            id: "weight-audit",
            title: "Velicina JS datoteke",
            failureTitle: `JS bundle exceeds your threshold of ${
                process.env.MAX_BUNDLE_SIZE_KB
            }kb`,
            description: "Alooo bato, ajmo malo crossfit za sajt!",
            // Sakpuljac (Getherer)
            requiredArtifacts: ["devtoolsLogs"]
        };
    }

    static async audit(artifacts, context) {
        // Uzmi Devtools log
        const devtoolsLogs = artifacts.devtoolsLogs["defaultPass"];
        // Uzmi network tab
        const networkRecords = await artifacts.requestNetworkRecords(devtoolsLogs);

        // Nadji sve resurse koji su tipa `Script` i podudaraju se sa nasim imenom
        const bundleRecord = networkRecords.find(record =>
                record.resourceType === "Script"
            &&  new RegExp(process.env.BUNDLE_NAME).test(record.url)
        );

        // Proveri da li je test prosao
        const belowThreshold =  bundleRecord.transferSize <= process.env.BUNDLE_SIZE * 1024;

        return {
            rawValue: (bundleRecord.transferSize / 1024).toFixed(1),
            // Rezultat se vraca izmedju 0 i 1, posto kod nas to moze da bude samo true (1) ili false (0)
            // Prebacicu rezultat iz Boolean u Number.
            score: Number(belowThreshold),
            displayValue: `${bundleRecord.url} je ${(bundleRecord.transferSize / 1024).toFixed(1)}kb`
        };
    }
}

module.exports = WeightAudit;

Malo da objasnim o čemu se radi. Na početku definišem meta podatke kao što su ime, opis, poruku ako je pao test i, najbitnije, sakupljač podataka (gatherer).

Posotji opcija da se ručno piše sakpuljač podataka od nule, ali pošto su nama potrebni podaci koji su već dostupni u devtools, nema potrebe da pišemo naš "gatherer". Evo primera kako se piše.

Zatim pišemo naš test koji treba da vrati objekat sa nekim definisanim svojstvima. Bitno je da napomenem da se rezultat vraća kao vrednost od nula do jedan iako je u testu prikazana kao 0 - 100.

Sad kad smo definisali test ostalo je još da kažemo Lighthouse-u da pokrene test. To se radi tako što prilikom pokretanja lighthouse CLI prosledimo --config-path putanju do naše datoteke. Dobijamo sledeću komandu:

command: |
  BUNDLE_NAME="$(node -p 'require("./package.json").lighthouse.bundleName')" \
  BUNDLE_SIZE="$(node -p 'require("./package.json").lighthouse.bundleSize')" \
  lighthouse $TEST_URL \
      --port=9222 \
      --chrome-flags=\"--headless\" \
      --config-path=./.circleci/lighthouse/weight-audit-config.js \
      --output-path=/home/chrome/reports/anonymous-"$(echo -n $CIRCLE_SHELL_ENV | md5sum | awk '{print $1}')" \
      --output=json \
      --output=html

Ovaj naš test će se sada pojaviti i u HTML izveštaju.

Autentifikacija

Odužio se ovaj članak, ali evo još malo kraja. Za kraj izborićemo se sa najvećim problemom, autentifikacija. Verovano se velika većina tokom čitanja ovog teksta pitala "šta je sa stranicama koje imaju autentifikaciju?"

Ovo je odlično pitanje koje je bitno da rešimo. Zato što je bitno da možemo da testiramo i delove sajta koji su iza prijave za korisnika.

Ligthouse pruža --extra-headers opciju, ali to znači da moramo ručno da generišemo tokene. Tokeni ističu što znači test može da padne ako zaboravimo da ažuriramo token. Ovo rešenje nije prihvatljivo.

Pošto imamo pristup NodeJS-u možemo ručno da punimo localStorage ali to neće da funkcioniše za svaki projekat, takođe može da bude poprilično naporno za održavanje.

Rešenje do kojeg sam ja došao je malo "hacky", zato što se oslanja ne redosled poziva "gatherer-a" i njegovih "hookova" da simulira kao da smo otvorili tu stranicu.

Napisaću skupljać koji ništa ne skuplja ;) već koristim beforePass "hook" i da unutar njega podignem puppeteer, ulogujem se i onda se pokrene test.

class Auth extends Gatherer {
    async beforePass(options) {

        const ws = await options.driver.wsEndpoint();

        const browser = await puppeteer.connect({
            browserWSEndpoint: ws,
        });

        const page = await browser.newPage();
        await page.goto(process.env.TEST_URL);

        await page.click('input[name=username]');
        await page.keyboard.type(process.env.ADMIN_USER);

        await page.click('input[name=password]');
        await page.keyboard.type(process.env.ADMIN_PASSWORD);

        await page.click('button[type="submit"]');
        await page.waitForSelector('#logout');

        browser.disconnect();
        return {};
    }
}

Reference

1: Ako je brzina konekcije 1.6 Mbps (MegaBITA po sekundi) mora da se prebaci u MB/s (MegaBAJTA po sekundi) što znači da delimo sa 8. Krajnja jednačina je 1.6 / 8 * 2 = 0.4 Mb = 400 Kb]]>
<![CDATA[Zašto JA ne volim jQuery]]>https://ilic.ninja/zasto-ja-ne-volim-jquery/5d2d93c65b00fa411f85da1fWed, 17 Jul 2019 17:27:06 GMTSvi, koji me iole poznaju, znaju da sam velikim protivnik jQuery-a. Ovaj članak će objasniti razloge zašto, a takođe će meni služiti kao referenca kada me neko pita "a što ga ja koristim svaki dan?".

1. Prošlo vreme

Kada se jQuery pojavio, još davne 2006. godine, ja još nisam ni počeo da učim veb tehnologije. U tom trenutku je jQuery bio san svakog developera, zato što je nastao kao rešenje jednog mnogo velikog problema - IE6. Internet Explorer 6 je bilo nešto najbliže paklu što smo videli u vebu. Ja sam imao malo dodirnih tačaka sa njim ali sam upoznat sa skoro svim "hakovima" koji su nastali kao rezultat tog veb pregledača.

jQuery je igrao jednu važnu i veliku ulogu, a to je standardizacija. Omogućio je da se developeri osećaju sigurnije zato što je kod radio na IE7 (2007. godina) a i, posebno, na IE6.

Na našu sreću IE6 je "mrtav". U međuvremenu, javaskripta je dobila dosta novina i vredno se radi na usavršavanju javaskripte i stoga korišćenje jQuery-a u nema smisla niti prednosti.

Tako da jQuery jeste odlična biblioteka, za vreme kada je bilo potrebna. Danas, mislim da svi možemo da se složimo da smo prerasli jQuery. Ali svakako, hvala mu za pomoć kada je to bilo potrebno.

2. Uništava zajednicu

Ovo nije toliko jQuery problem, već problem sa ljudima koji ga koriste.

Novi developeri koji kreću sa javaskriptom obično počinju sa jQuery-jem i potpuno se oslanjaju na njega. Iako sve ono za šta koriste u jQuery-ju je moguće podjednako lako pa čak i brže uraditi u vanila javaskripti. Gore sam pomenuo da je jQuery nastao kao biblioteka da pomogne developerima u najtežem periodu podrške veb pregledača, i iz tog razloga sa sobom nosi mnogo koda koji se nikad ne koristi.

Za takve developere koristim izraz full jQuery developer™.

Poruka od mene za full jQuery developere je da se  oslobode i probaju vanilla javaskript, jer skoro pa sve što pruža jQuery možemo lako implementirati:

  • $() selektor === querySelectorAll()
  • Dodavanje clasa addClass(), removeClass() === classList
  • Dodavanje događaja on(event) === addEventListener() je svuda podržan

Da ne bih pisao šta sve može, pogledajte na youmightnotneedjquery.

3. Performanse

Ono što je po mom mišljenju najviše ubilo jQuery jeste pojava "pametnih" telefona koji imaju pristup internetu. Prvi telefoni su bili 10, a možda čak i 100 puta sporiji nego računari u to vreme. jQuery zbog velike količine koda (150-230kB) predstavljao je usko grlo za telefone.

Mislim da je bitno da napomenem da jQuery-a zauzima dosta veliki deo koda. Neretko viđam da je jQuery uključen samo radi nekog plugina, recimo u primeru ispod bxslider.

jQuery zauzima više od 60% izlaznog koda.

Zaključak

Nemam ništa protiv jQuery, čak sam mu mnogo zahvalan iako mene nije zahvatio taj najteži period veb pregledača. Pomogao mi je dosta prilikom pravljenja veb stranica za IE9. Ipak, moje mišljenje je da smo ga odavno prerasli i da više nema smisla koristiti ga jer postoji toliko novih biblioteka koje su odgovor za današnje probleme (SPA, dinamički sadržaj). Verovatno će kroz 10 godina Vue, Angular i React biti zastarele biblioteke kao jQuery danas, što ne znači da su one loše, već da su samo bile odgovor na aktuelne probleme koji možda u budućnosti neće postojati.

]]>
<![CDATA[JS Savet: Merenje performansi]]>https://ilic.ninja/js-savet-merenje-performansi/5d25f02c5b00fa411f85d7cfThu, 11 Jul 2019 09:48:40 GMTU javaskripti često postoji više načina za implementaciju iste funkcionalnosti, na primer, dodavanje elemenata u DOM. Kada se nađemo u takvoj situaciji, teško je odlučiti koju implementaciju da iskoristimo. Iz tog razloga, javaskripta nam pruža Performance API, pomoću kog možemo meriti performanse.

Merenje performansi

Recimo da hoćemo iskoristiti naš primer od ranije, dodavanje elemenata u DOM. Postoji par načina da se to implementira

  1. Korišćenjem innerHTML
  2. Korišćenjem createElement() i appendChild()
  3. Korišćenjem insertAdjacentHTML()

Svaki test će da doda 1000 DOM (li) elemenata. Postavka je sledeća

<ul id="list">
    <!-- Ubaci stvari ovde -->
</ul>

Testovi izgledaju ovako. Za prvi slučaj:

const list = document.getElementById('list');

const start = performance.now();
let html = '';
for(let i = 0; i < 1000; i++) {
    html += '<li>' + i + '</li>';
}

list.innerHTML = html;

const end = performance.now();
console.log('innerHTML ' + (end - start) + 'ms');

Ako napišemo kôd za svaki od slučajeva, dobijamo sledeći rezultat:

Slučaj Vreme
innerHTML 3.4549999982118607ms
appendChild 4.28000000101747ms
insertAdjacentHTML 2.8750000019499566ms

Zahvaljujući performans APIu smo lako videli koja implementacija je najbrža.

Implementacija

Možda se pitate zašto nam je potreban još jedan intefejs, kada imamo Date.now(). Postojeći Date interfejs vraća vreme u milisekundama što je već dovoljno precizno.

const start = Date.now();
longToDoTask();
const duration = Date.now() - start;

U ovom slučaju su milisekunde dosta neprecizna jedinica. Ono što performanse API koristi su isto milisekunde, ali na drugačiji način. Metoda performance.now() vraća DOMHighResTimeStamp, što garantuje preciznost u 5 mikrosekundi.

Još jedna, verovatno mnogo bitnija razlika je, to što se vrednost performance.now() metode konstantno povećava, nezavisno od sata na računaru. To ustvari i jeste glavni problem kod Date.now() metode. Ako tokom testiranja izmenimo vreme na računaru, prilikom uzimanja krajnjeg vremena naše merenje neće biti tačno.  

Eksterni alati

Ako ne želimo da radimo testove lokalno ili pak želimo da podelimo testove sa nekim da i on može testirati, možemo da koristimo eksterne alate za testiranje. Jedan on najstarijih i sigurno najpoznatijih je JSPerf. Iako je par puta bio nedostupan zbog problema servera i finansijskih problem, ostao je sigurno jedan on najboljih.

Evo kako na JSPerfu izgleda naš primer, https://jsperf.com/inesering-dom. Rezultati su isti, samo je sada merena jedinica broj operacija u sekundi.

Slučaj Ops/s
innerHTML 19,201
appendChild 53,707
insertAdjacentHTML 57,434

Eksterna biblioteka

Kad već pričamo o merenju performansi, mislim da je bitno pomenuti biblioteku koja predstavlja zlatan standard za merenje. Biblioteka Benchmark.js nam pruža značajno veću fleksibilnost nego kada sami koristimo Performans API, zato što se kod izvršava više puta, sve dok se ne dođe do minimalnog vremena, potom se to pokrene više puta kako bismo dobili prosek.

JSPerf koristi Benchmark.js kao glavnu biblioteku za testiranje. Zato je moje lično mišljenje da je bolje koristiti JSPerf nego sami da pišete testove koristeći Benchmark.js. JSPerf ima jednu veliku prednost, a to je da možemo lako podeliti test sa nekim i da testove možemo da pokrenemo i uporedimo na različitim veb pregledačima.
]]>
<![CDATA[Privatnost u Javaskripti, Closure]]>https://ilic.ninja/privatnost-u-javaskripti/5caa014e3fd4a171590988c1Sat, 29 Jun 2019 10:27:31 GMTU prethodnom članku, "Haos u oblasti (ne)definisanosti", smo upoznali kako zaista funkcioniše oblast definisanosti u Javaskripti i koji su česti problemi. Zbog svih navedenih problema i ideje da se Javaskript približi ostalim programskim jezicima, javila se potreba za privatnošću u Javaskripti.

Šta je privatnost?

Ideja privatnosti je da samo određeni članovi iz određene oblasti mogu da pristupaju promenjivama, dok je pristup ostalim članovima van te oblasti zabranjen.

Nakon onoga što smo naučili, nije tako teško postići privatnost u Javaskripti, zar ne?

function foo() {
    var privatno = 'tajna';
    
    console.log(privatno);
}

foo(); // 'tajna'
privatno; // Reference error

Ovim smo efektivno sakrili promenjivu privatno, jer zbog oblasti važenja ova promenjiva samo postoji unutar funkcije foo.

Kraj? Ne bih rekao. Problem je u tome što ova promenjiva živi samo dok i funkcija živi i ako želimo da radimo još nešto sa njom to neće biti moguće.

Ako se malo prisetimo kako radi leksička oblast, a to je tako što pita na gore da li neko ima tu referencu, shvatamo da upravo taj mehanizam možemo iskoristiti.

function foo() {
    var privatno = 'tajna';
    
    function bar() {
        console.log(privatno);
    }
    
    bar();
}

foo(); // 'tajna'

Iz ovog primera vidimo da unutrašnja funkcija ima pristup članovima "iznad" nje. Ovo je odlično ali ne rešava naš problem zato što mi nemamo pristup već se ovo izvrši jednom i zatim obriše. Očigledno, neophodno nam je drugačije rešenje.

Closure

Rešenje za ovaj problem je Closure, vrlo česta reč u kombinaciji sa Javaskript-om i razgovorom za posao. Veliki broj ljudi smatraju da je ovo neophodno znati i često se forsira kao eliminaciono pitanje na razgovoru za posao. Videćemo da ovo uopšte nije strašno. Sada kad znamo kako radi oblast definisanosti, Closure je logična stvar.

Dakle, šta je closure? Funkcija koja vraća fukciju... Možda zvuči suviše prosto ali jeste upravo to.

function foo() {
    var privatno = 'tajna';
    
    function bar() {
        console.log(privatno);
    }
    
    return bar();
}

var bar = foo();
bar(); // tajna
bar(); // tajna

Ovim smo dobili primer pristup funkciji foo van nje. Kako ovo radi?

Kada mi pozovemo funkciju foo ona nama vrati referencu na funkciju bar. Samim tim što je vratila referencu na nešto unutar nje skupljač smeća (garbage collector) neće obrisati funkciju foo sve dok postoji referenca. Sve funkcije unutar funkcije foo znaju za njene promenjive, samim tim mogu i de pristupe njenom sadrzaju.

Petlje

Recimo da imamo ovaj kod:

for (var i = 0; i < 17; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000);
}

Ovaj kod sam iskoristio zato što se često javlja u projektima i predstavlja problem. Ovaj kod da loguje 17 puta broj 17. Čudno?

Ako pokrenemo linter nad ovim kodom, dobicemo no-loop-func error. Ovo je vrlo čest probelem i javlja se zbog nepoznavanja kako closure radi.

Ono što sigurno možemo da zaključimo je da se setTimeout pozvao posle petlje, to ima smisla. Zato što petlja ne čeka da se izvrši setTimeout već ide dalje. Šta više, sve setTimeout funkcije unutar sebe referenciraju na istu promenjivu, i u ovom slučaju je to stvarno referenca a ne kopija. Što znači, kad dođe vreme da se izvrši setTimeout to je posle petlje, a naša vrednost je već povećana.

Lako možemo da zaključimo da se ovo sve izvršava posle petlje tako što pogledamo krajnji broj. Nama je uslov i < 17, što znači da petlja treba da stane kad je broj i = 16. To jeste istina, ali kada petlja stane i = 17 zato što se i++ izvršilo u prethodnom koraku.

Ono što nama treba ješte da postoji kopija i u svakoj iteraciji, čime postižemo da je i jedinstveno i ne zavisi od petlje. Sad kad znamo šta je Closure i kako radi hajde da probamo to rešenje.

for (var i = 0; i < 17; i++) {
    (function() {
        setTimeout(function () {
            console.log(i);
        }, 1000);
    })()
}

Da skratim muke pokretanja ovog koda, ovo neće raditi. Vidimo da iako imamo leksičku oblast važenja pomoću IIFE ovo ne rešava problem. Neki već sad vide rešenje, a problem je u tome što je naša oblast prazna. Ona i dalje referencira istu promenjivu.

for (var i = 0; i < 17; i++) {
    (function() {
        var j = i;
        setTimeout(function () {
            console.log(j);
        }, 1000);
    })()
}

Napokon, ovo radi i dobijamo izlaz brojeva od 0 do 16. Ono što je bilo bitno je da prilikom svake iteracije mi unutar funkcije napravimo novu kopiju za promenjivu i. Ovaj trenutni kod možemo da poboljšamo tako što i prosledimo kao vrednost funkcije. Ovo će raditi zato što JS prosleđuje primitivne promenljive po vrednosti a ne po referenci.

for (var i = 0; i < 17; i++) {
    (function(j) {
        setTimeout(function () {
            console.log(j);
        }, 1000);
    })(i)
}

Elegantnije rešenje?

Iako je rešenje gore potpuno validno i tačno, moguće je značajno poboljšati kod. Ono što je bilo potrebno da bismo rešili problem je da za svaku iteraciju napravimo blok oblast važenja, što već znamo da uradimo zahvaljujući ES6 (živ bio i veliki porasooo!!!).

for (let i = 0; i < 17; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000);
}

Ako otvorimo specifikaciju i pogledamo definiciju

for ( LexicalDeclaration Expressionopt ; Expressionopt )

U drugom koraku kaže Let loopEnv be NewDeclarativeEnvironment(oldEnv)., što znači da za svaki korak u petlji se definiše nova leksička oblast koja je ulančana sa starom.

Pravljenje nove oblasti i ulančavanje sa starom je nekad bila vrlo skupa operacija. Ako pokrenemo dve petlje sa potpuno istim sadržajem a jedina razlika je da jedna koristi var a druga pre možemo da vidimo da danas daju jednake rezultate. Ali u ranijim implementacijama var petlja je bila preko dva puta brža.

UserAgent Petlja sa var Petlja sa let
Chrome 48.0.2564 5,891,754 ops/sec 11,683,983 ops/sec

Testovi su napisani i testirani na JSPerf. Takođe, ovaj bug je bio prijavljen Chrome timu.

]]>
<![CDATA[JS Savet: Uslovno dodavanje svojstva na objektu]]>https://ilic.ninja/js-savet-uslovno-dodavanje/5d0262d65b00fa411f85d48eThu, 13 Jun 2019 16:12:29 GMTČesto se javlja potreba da dodamo neko svojstvo objektu koje zavisi od nekog uslova.

const nickname = req.body.nick;

const formData = {
    name: 'John',
}

if (nickname) {
    formData['nickname'] = nickname;
}

Ovo je najčešći način za dodavanje svojstva objektu. Na ovaj način činimo da kôd bude manje čitljiv i definicija objekta (strukture) se dešava na više mesta.

ES6 u pomoć

Ako iskoristimo pametno "spread" operator možemo značajno da popravimo kôd.

const nickname = req.body.nick;

const formData = {
    name: 'John',
    ...nickname && {nickname: nickname}
}

Ovim smo primetno skratili naš kôd i definicija strukture je na jednom mestu.

Leva strana može da bude bilo koji logički izraz, tako da mogu da se nađu i složeni izrazi. Na primer ako imamo žetone, gde je vrednost može da bude 0 ili neki drugi broj koji je "false". Uslov može ovako da izgleda ...typeof zetoni === "number" && !isNaN(zetoni) && {zetoni},

Kako ovo radi

Prvo da se upoznamo sa && operatorom. Ako zagledamo malo u specifikaciju && operatora, možemo da vidimo da će vrednost biti jedna od vrednosti operatora.

Vrednost koja je rezultat && ne mora da bude boolean tipa.

Ukoliko leva strana nije istinita, desna strana se neće evaluirati i rezultat operacije biće "false". I suprotno, desna strana će se evaluirati samo ako je leva strana istinita. Ovo znači da će naš "spread" operator dobiti levu vrednost false, ili desnu vrednost - objekat.

Kako ovo stvarno radi

Ovo je malo naprednije objašnjenje šta se stvarno dešava i oslanja se na poznavanje ECMA specifikacije. Gore nije objašnjeno šta se desi kada se nađe "number" ili "boolean" vrednost pored "spread" operatora.

Šta može da se nađe sa desne strane "spread" operatora?

  1. null ili undefined
  2. Boolean i Number
  3. Object - Ovo je već poznato ponašanje

Kada pogledamo specifikaciju za "spread" operator vidimo da se pozove CopyDataProperties operator. Gledajući šta radi CopyDataProperties operator, možemo da vidimo da u koraku tri ako se nađe null ili undefined, operator neće uraditi ništa.

const obj = {
    ...null,
    ...undefined
}

console.log(obj) // {}

Ovim smo pokrili prvi slučaj, sada ostaje još drugi slučaj kad se nađu drugi tipovi koji nisu Object.

Ako pogledamo četvrtu stavku CopyDataProperties operatora, vidimo da se nad from (desna strana) zove ToObject. Što znači da će svaki tip biti obmotan u odgovarajući tip objekta. Ono što je bitno da znate je, da ovi objekti, Boolean i Number, sadrže nešto što se zovu interni slotovi. Ovim slotovima ne može da se pristupi već predstavljaju interno stanje objekta na nivou "engine"a. Slotovi u objektu se nazivaju između duplih uglastih zagrada [[imeSlota]].

console.log(new Number(17));
console.log(new Boolean(true));

/*
Number {23}
    __proto__: Number
    [[PrimitiveValue]]: 23
    
Boolean {true}
    __proto__: Boolean
    [[PrimitiveValue]]: true
*/

Primitivnim vrednostima ne možemo obično da pristupimo, već koristimo valueOf().

console.log(new Number(17).valueOf());
console.log(new Boolean(true).valueOf());

/*
    17
    true
*/

Ovo nama ide sve u prilog zato što objekat bez svojstva znači da CopyDataProperties operator nema šta da kopira.

Pokrili smo sve slučajeve, tako da smo sigurni koja god vrednost da dođe kao uslov neće da napravi problem objektu.

]]>
<![CDATA[SVG za dizajnere 🤜🤛️ developere]]>https://ilic.ninja/rad-sa-svgem-za-sve/5cb8d68b3fd4a17159098abcSun, 21 Apr 2019 11:50:00 GMTSVG nije novost u 2019. godini i tokom par godina je standard mnogo napredovao. Tako smo i dobili različite načine da koristimo SVG unutar HTML-a i CSS-a.

Proći ću kroz jedan konkretan i stabilan način za korišćenje SVG-a unutar HTML-a, tačnije kako napraviti sistem ikonica pomoću SVG-a. Debeda da li koristiti SVG ili font za ikonice je već mnogo puta vođena i moj savet je da koristite SVG. Ovaj članak obuhvata i dizajnere zato što su oni dužni da urade samu pripremu SVG-a.

Priprema

Kao i kod štampe, pre korišćenja vektorske slike, potrebno je uraditi grafičku pripremu. Iako je najlakše samo izvesti ikonicu iz našeg omiljenog alata (Illustrator, Sketch, Inscape itd.), uz malo truda, možemo mnogo olakšati developerima i sprečiti razne probleme na koje oni mogu naići.

illustrator (levo), Sketch (desno)

Svaka ikonica za sebe

Prilikom pravljenja ikonice, svaka ikonica treba da ima svoj zaseban "artboard" tj. radnu površinu. Ovim se osiguravamo da ikonica sadrži tačno ono što joj je potrebno i nema sakrivenih putanja u pozadini.

Oblik je bitan

Oblik same ikonice nije bitan. Ono što jeste bitno je da "artboard" u kome se nalazi ikonica bude kvadratnog oblika. Kada se ikonica nalazi u kvadratnoj radnoj površini to dosta olakšava rad sa ikonicom, jer možemo biti sigurni da su sve ikonice jednake visine i širine.

Primer SVG ikonica (svgicons)

Tačne dimenzije radne površine nisu definisane, ali postoji nepisano pravilo da to bude 16x16 ili 20x20 piksela zato što se ikonice najčešće koriste u toj izvornoj veličini.

Prostor da diše

Ovo je možda i najčešća greška kod dizajnera, a to je da se ikonica prostire od ivice do ivice "artboarda".

Kada veb pregledač prikazuje SVG ikonicu on koristi "anti-aliasing", ali često taj jedan dodatni piksel koji doda AA neće da se prikaže van našeg viewBoxa.

Ovaj problem je dosta vidljiv u Firefox i Edge pregledačima i, kada se javi, to izgleda ovako:

IcoMoon SVG ikonice na Firefoxu

Kako bismo izbegli ovaj problem, pridržavajte se pravila da ikonica ima 0.5px ili 1px prostora sa svake strane.


Izvoz ikonice

Kada smo napravili novu radnu površinu, a zatim napravili našu ikonicu i umanjili je za jedan piksel kao što je gore rečeno, tek tada smo spremni da izvezemo ikonicu iz našeg omiljenog alata. Ovaj proces će zavisiti od alata do alata, ja ću konkretno pokriti dva najkorišćenija alata, Sketch i Illustrator.

Sketch

Prilikom izvoza iz Sketch-a može doći do poznatog problema da Sketch ubaci nepotreban markup koji će da nam poveća veličinu datoteke. Osim same veličine datoteke ovakav markup može da napravim problem prilikom korišćenja ikonice.

Evo kako izgleda loša ikonica iz Sketch-a:

<?xml version="1.0" encoding="UTF-8"?>
<svg width="100px" height="94px" viewBox="0 0 100 94" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <!-- Generator: Sketch 54.1 (76490) - https://sketchapp.com -->
    <title>logo-m</title>
    <desc>Created with Sketch.</desc>
    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g id="Artboard" transform="translate(0.000000, -3.000000)" fill="#1A1A1A" fill-rule="nonzero">
            <g id="logo-m" transform="translate(0.000000, 3.000000)">
                <path d="..." id="Shape"></path>
            </g>
        </g>
    </g>
</svg>

Možemo primetiti da postoji veliki broj grupa <g> i ono što često pravi problem je  transform koji ustvari ničemu ne služi. Ako pogledamo malo bliže, videćemo da jedan drugog poništavaju. Ovaj primer zna biti mnogo lošiji, sa mnogo više grupa.

Zašto se ovo tačno dešava ne mogu reći sa sigurnošću, ali sam zaključio da se dešava kada izvozimo "shape" a ne "artboard", što bismo svakako trebali da radimo.

Pravi način da izvezemo ikonicu je da izaberemo "artboard" a zatim opciju "Make Exportable", dok za format izaberemo "SVG".

Ikonice koje Sketch izveze nisu optimizovane. Videćemo kasnije kako da ih optimizujemo.

Illustrator

U Illustrator-u je ovaj proces dosta jednostavniji i Illustrator nema probleme sa grupama i transformacijama. Idemo na File > Export > Export As... i jedina bitna stvar je da izaberemo u dnu "Use Artboards".

Ikonice koje Illustrator izveze su optimizovane. Moguće je optimizovati ih dalje ali ima minimalnih dobitka.
Kad izvozimo u bilo kom alatu, bitno je da naglasimo da izvozimo "artboard" a ne samo ikonicu. Zato što ako se setimo onog pravila da umanjimo ikonicu za par piksela, to više neće važiti ako izvezemo samo ikonicu. Zato moramo da izvezemo ceo "artboard" koji u sebi ima ikonicu manju za par piksela.

Optimizacija SVGa

Kad smo uspešno izvezli ikonice vreme je da dodatno sačuvamo prostor tako što ih optimizujemo.

SVG datoteke obično sadrže dosta redundantnih i beskorisnih informacija. Često su to metapodaci, komentari, skriveni elementi i druge stvari koje se mogu sigurno ukloniti ili pretvoriti bez uticaja na rezultat SVG-a.

Trenutni zlatni standard je SVGO koji je konkretno NodeJS alat, ali dolazi u različitim oblicima.

Komandna linija:

Unutar foldera gde su nam ikonice pokrenemo svgo *.svg i ovo će nam optimizovati sve ikonice.

Ostali alati:

Uklanjanje boje

Ikonica u sebi ne bi trebala da ima definisanu boju. Ako ikonica ima "hardkodiranu" boju unutar sebe, nećemo moći da izmenimo boju ikonice preko CSS-a, što znači da gubimo kontrolu nad bojama. Zato je uvek dobro da sklonimo boje.

Postoje dva načina da se dodeli boja u SVG-u:

  • Preko atributa fill.
  • Preko CSS-a koji je unutar SVG-a ( style blok).

Bitno je da naš SVG ne sadrži ni jedan fill atribut, niti style blok.

Illustrator ne dodaje fill attribut putanjama koje su pune crne boje (#000000). Zato se generalno preporučuju da ikonice budu potpuno crne. Za sketch ovo ne važi, već moramo ručno obrisati.

Sistem ikonica

Krećemo u developerski deo posla. Ako niste developer ili jednostavno niste zainteresovani šta sve developeri treba da urade da bi prikazali ikonice na sajtu, ovaj deo nije za vas.

Sistem ikonica pravimo pomoću "sprajta" (ne to nije sok). Sprajt je jedna velika slika koja sadrži sve moguće sličice tako da se znaju kordinate svake sličice i na taj način se vrši mapiranje. Ova tehnika postoji još od 70ih godina, kada je Atari, jedan od prvih, implementirao ovo.

Sprajt za Pac-Man (Atari 7800)

Malo preciznija i slobodnija definicija u našem slučaju bi bila mapa simbola, zato što je mapa ustvari jedan SVG koji ima definisane simbole.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
  <path d="M16.5 18.8c-.1-.8-1-.9-1.5-.8-.5-2 1.3-5 1.4-5.8.1-.8-.5-5.2-.6-7.5-.1-2.3-3.4-4.9-4-4.5-.6.6.8 1.4.1 2.2-.7.7-.9 1.2-1.4 1.9-1.3 2 2.3 2 2.3 2S10 8.5 9.4 9.5c-.6 1-.8 2-1.5 2.9-1.6 2.1-1.2 5.7-1.2 5.7s-3-1-1.3-4.4c1.2-2.6 1.9-6.4-.9-5.4-1.2.4.1 1.2.3 1.5.8.9.1 2.3-.3 3.1-1.2 2.2-1.7 4.6.9 6 1.2.6 2.7.8 4.3.8h4c1.3.1 2.8-.1 2.8-.9z"/>
</svg>

To izgleda ovako:

Dodavanje ikonice na mapu

Vrlo je jednostavno dodati ikonice na mapu, kao što sam rekao, SVG mapa je SVG sa simbolima.

<svg xmlns="http://www.w3.org/2000/svg">
  <symbol id="macka" viewBox="0 0 20 20">
    <path d="M16.5 18.8c-.1-.8-1-.9-1.5-.8-.5-2 1.3-5 1.4-5.8.1-.8-.5-5.2-.6-7.5-.1-2.3-3.4-4.9-4-4.5-.6.6.8 1.4.1 2.2-.7.7-.9 1.2-1.4 1.9-1.3 2 2.3 2 2.3 2S10 8.5 9.4 9.5c-.6 1-.8 2-1.5 2.9-1.6 2.1-1.2 5.7-1.2 5.7s-3-1-1.3-4.4c1.2-2.6 1.9-6.4-.9-5.4-1.2.4.1 1.2.3 1.5.8.9.1 2.3-.3 3.1-1.2 2.2-1.7 4.6.9 6 1.2.6 2.7.8 4.3.8h4c1.3.1 2.8-.1 2.8-.9z"/>
  </symbol>
</svg>

Možda izgleda kao da se svašta desilo, ali je ustvari prilično jednostavno. Samo treba da prebacimo <svg viewBox="..."> u <symbol id="..." viewBox="...">.

Ovim smo SVG ikonicu prebacili u simbol i dodelili mu jedinstven selektor (id). Ovo treba uraditi za svaku ikonicu. Kako to može biti mukotrpan proces, postoje alati za koji to mogu uraditi umesto nas.

Upotreba ikonice

Ikonice se dodaju pomoću <use> elementa, ali postoje dva načina da referenciramo sprajt:

  • Interni sprajt
  • Eksterni sprajt

Eksterni sprajt

Kao što ime kaže, sprajt se nalazi u eksternoj datoteci, recimo ikonice.svg

<svg>
  <use xlink:href="/putanja/do/ikonice.svg#macka"></use>
</svg>

Ovim smo uzeli ikonice.svg i iz tog SVG-a i uzeli simbol sa ID-em macka. Ovaj pristup zahteva jedan dodatni HTTP zahtev da pribavi taj fajl. Obično taj fajl nije toliko veliki pa zato preporučujem interni sprajt.

Interni sprajt

Umesto da idemo po fajl, možemo staviti SVG sa simbolima negde na početku stranice, a zatim da koristimo ikonice iz istog.


<!DOCTYPE html>
<html>
<head>
    <title>Nas sajt</title>
</head>
<body>
    <!-- Nas interni sprajt sa ikonicama -->
    <svg style="display: none">
        <symbol id="macka" viewbox="0 0 20 20">
            <path d="M16.5 18.8c-.1-.8-1-.9-1.5-.8-.5-2 1.3-5 1.4-5.8.1-.8-.5-5.2-.6-7.5-.1-2.3-3.4-4.9-4-4.5-.6.6.8 1.4.1 2.2-.7.7-.9 1.2-1.4 1.9-1.3 2 2.3 2 2.3 2S10 8.5 9.4 9.5c-.6 1-.8 2-1.5 2.9-1.6 2.1-1.2 5.7-1.2 5.7s-3-1-1.3-4.4c1.2-2.6 1.9-6.4-.9-5.4-1.2.4.1 1.2.3 1.5.8.9.1 2.3-.3 3.1-1.2 2.2-1.7 4.6.9 6 1.2.6 2.7.8 4.3.8h4c1.3.1 2.8-.1 2.8-.9z" />
        </symbol>
    </svg>
    <!-- Ovde neki HTML -->
    <svg><use xlink:href="#macka"></use></svg>
</body>
</html>

Razlike

Osim što je drugačiji stil pisanja, postoji velika razlika između ova dva načina. Jedna od glavnih je podrška pregledača:

Chrome Edge Firefox IE Opera Safari
Eksterni sprajt 36 Ne 15 Ne 23 7.1
Interni sprajt 36 9 15 12 23 7.1

Više na Can I Use.

Imajte na umu da interni sprajt nije moguće keširati, dok će se eksterni sprajt keširati od strane pregledača.

Ako baš želimo da koristimo eksterni sprajt i IE možemo da uključimo svg4everybody "polifil".

Stilizovanje ikonica CSS-om

Sad kad smo dodali SVG ikonice i naučili različite načine kako da ih uključimo u HTML, vreme je da pređemo na CSS. SVG ikonice koje su deo mape možemo posebno da stilizujemo kao da su ustvari posebno uključene i tu leži sama moć mape, mogućnosti internog SVG-a bez ponavljanja.

.icon {
    fill: dodgerBlue;
    height: 30px;
}

Ako dodamo klasu .icon na našu ikonicu <svg class="icon"><use xlink:href="#macka"></use></svg> dobijamo sledeći razultat.

Kako stalno ne bismo pisali fill , height i duplirali kod za color ovo može lepše da se standardizuje.

.icon {
    fill: currentColor;
    width: 1em;
    height: 1em;
    vertical-align: middle;
    overflow: hidden;
}

Ovim će se naša ikonica ponašati kao i font.

Macka
Macka
]]>
<![CDATA[Haos u oblasti (ne)definisanosti]]>https://ilic.ninja/haos-u-oblasti-definisanosti/5ca9ba383fd4a171590985fcSun, 07 Apr 2019 18:29:25 GMTČesta greška je da ljudi smatraju da je Javaskript interpreterski jezik, što nije slučaj. Javaskript se ustvari kompajluje. Pre samog izvršavanja izvorni kod se šalje kompajleru gde se izvršavaju različiti procesi. Tokom kompajliranja obavlja se proces leksiranja(tokenizacije) što znači da se definiše oblast važenja promenjivih. To znači da je leksička oblast važenja ustvari oblast važenja koja se određuje prilikom kompajliranja.

Kako bismo razumeli proces leksiranja, najbolje je da postavimo primer. Ako posmatramo sledeći izraz var m = 17, na prvi pogled on izgleda kao jedan izraz ali ovaj proces može da se podeli u dva koraka:

  1. var m Deklaracija promenjive unutar oblasti definisanosti
  2. m = 17 Dodela vrednosti promenjivoj, 17.

Tokom procesa komajliranja, tačnije u procesu leksiranja, kompajler će da odluči kako i gde su definisane sve promenjive. Ovo je onaj isti mehanizam koji izaziva podizanje (hosting) o kome sam pričao u funkcijama u Javaskriptu.

Vrlo je važno odvojiti deklaraciju od dodele vrednosti zato što se te dve stvari dešavaju u potpuno različitom trnutku u vremenu. Ako iskoristimo sledeći primer:

console.log(m)
var m = 17

Šta će ovo da ispiše? Postoje tri mogućnosti:

  1. Reference error
  2. Undefined
  3. 17

Tačan odgovor je undefined, upravo iz razloga što se definicija promenjive dešava tokom leksičke (kopajlerske) faze. Dok se dodela vrednosti dešava tokom dinamičke faze.

Kada smo razjasnili da Javaskript nije interpreterski jezik, već se kompajluje i kombinacija je statičkog i dinamičkog izvršavanja, možemo da pređemo na oblast definisanosti.

Oblast definisanosti (scope)

Javaskripta ima dosta problema što se tiče oblasti definisanosti, pa zato sam i odlučio da sve to opišem ovde, ali ono što je možda i najveća inicijalna greška Javaskripte je da je zamišljena da leksička oblast definisanosti bude nad funkcijom, iako većina jezika imaju oblast definisanosti nad blokom (blokom se smatraju { } zagrade). Na žalost, još veći problem je što uprkos inicijalne ideje da oblast definisanosti bude nad funkcijama, videćemo da to nije tačno i da je moguće prevariti taj mehanizam.

Oblast definisanosti nad funkcijama

Kao što sam već rekao, ideja je da oblast definisanosti bude nad funkcijma, a evo i par primera:

var m = 17 // Globalna oblast

function foo() {
    var ninja = "tajna" // Foo oblast
}

console.log(m) // 17
console.log(ninja) // ReferenceError: ninja is not defined

Ono što ovde vidimo je da je var m definisano u globalnoj oblasti, što znači da je dostupno bilo gde u kodu, dok je sa druge strane var ninja definisano unutar funkcije što znači da je oblast važenje te promenjive samo unutar te funkcije. Ovaj primer je "idealan" primer gde se sve lepo izvršava, ali hajde da vidimo kako izgleda tok izvršenja.

Razmišljaj kao kompajler

Recimo da imamo sledeći primer:

var m = 17
var bar = 'bam'

function foo(m) {
    var bam = 'hi'
    m = 9
    n = 'fun'
}

Šta će ovde da se desi? Prvo će se izvršiti korak leksike, gde će kompajler da prođe i nađe sve definicije novih promenjivih.

Kompajler:
Linija 1: Nova promenjiva m, definiši je na globalnom nivou.
Linija 2: Nova promenjiva bar, definiši je na globalnom nivou.
Linija 4: Nova funkcija foo, definiši je na globalnom nivou.
Linija 4: Nova promenjiva m, definiši je na nivou funkcije foo.
Linija 5: Nova promenjiva bam, definiši je na nivou funkcije foo.

Ovim je završena leksička faza i sve promenjive su definisane.

Zatim dolazi druga faza kompajliranja a to je dodela vrednosti.

Interpreter:
Linija 1: Global jel imaš m? Da. Dodeli mu ovu vrednost.
Linija 2: Global jel imaš bar? Da. Dodeli mu ovu vrednost.
Linija 5: Foo jel imaš bam? Da. Dodeli mu ovu vrednost.
Linija 6: Foo jel imaš m? Ne. Global jel imaš m? Da. Dodeli mu ovu vrednost.
Linija 7: Foo jel imaš n? Ne. Global jel imaš n? Ne... Šta onda?

Ovo je ono gde nastaje problem a to je da će global da kaže:

Nemam ni ja tu promenjivu, hajde da ti je napravim... Izvoli!

Šta se sada deslio? Došlo je do kršenja pravila oblasti važenja unutar funkcije, naša promenjiva n je postala globalna iako se nalazi unutar funkcije.

Ovo je lako rešivo tako što koristimo strict režim.

Ovo je prvi problem, gde vidimo da oblast važenja nad funkcijom više ne važi.

Ipak nije samo funkcija

Na početku sam rekao da je ideja bila da oblast važenja bude nad funkcijama. To se promenilo u verziji ES3 kad je uveden try catch blok.

Ono što mali broj ljudi zna je da catch ima svoju oblast važenja.


try {
  throw new Error()
} catch(e) {
    console.log(e) // Error
}

console.log(e)

Ako se vratimo na prethodnu priču, ovo će i biti logično, zato što catch gledamo kao funkciju, što znači da je parametar funkcije lokalna promenjiva u odnosu na funkciju.

Ja lično ovo ne smatram kao "varanje" oblasti važenja zato što, ako posmatramo na pretohdno opisan način, onda ovo ima smisla. Ali postoje načini da se prevari oblast važenja.

Prevara oblasti važenja

Postoje dva načina da se prevari oblast važenja u Javaskripti ali oba ova načina treba izbegavati i ne preporučujem da se ikad koriste.

Zlo - Evil

eval() - Funkcija koja je češće poznatija kao evil (zlo) i to iz više razloga, je funkcija koja izvršava string kao Javaskript kod. Često se koristi kao primer za pravljenje digitrona gde konstruišemo string od brojeva i operatora zatim to prosledimo evalu na izvršenje.

eval("2+3-1") // 4

Problem je što eval izvršava bilo šta što mu pošaljemo, eval("alert('cao')") - ovo će da izbaci alert. Često je eval bio problem za XSS. Toliko je loše da ima posvećenu sekciju na MDN-u, Do not ever use eval!

Kakve veze ima sa oblašću važenja? I te kako ima veze, zato što eval pravi svoju oblast važenja:

var m = 17

function foo(str) {
    eval(str) // NIKAD OVO
    console.log(m)
}

foo('var m = 42');

Verovatno može da se zaključi izlaz ovog, a to je 42... Ovo se dešava zato što eval menja leksičku oblast važenja i izgleda kao da je var m = 42 bilo tu tokom kompajlovanja programa. Ovde takođe postoji problem optimizacije zato što poziva interpreter i nema optimizacije na nivou kompajlera.

With

with ključna reč se retko viđa, ja je iskreno nikad nisam video u produkciji, a videćemo da je to dobro. Ali može da se nađe u golfjs takmičenjima ili 1 kB, 13 kB Javaskript takmičenjima.

with nam omogućava da skratimo kod tako što prosledimo objekat za koji može da se evaluira oblast važenja:

var obj = {
    a: 17,
    b: 42
}

obj.a = obj.a + obj.b
obj.b = obj.b - obj.a

// Ovo može kraće da se napiše bez `obj`-a.

with (obj) {
  a = a + b
  b = b - a
  c = 1
}

console.log(obj.c) // undefined

with se u ovom slučaju ponaša tako što pravi svoju leksičku oblast definisanosti. To znači kao i do sada, ako nešto kad naiđe na vrednost prvo pita lokalnu oblast, što je parametar koji smo prosledili obj, ako ne može tu da nađe onda ide dalje globalno. I tu se javlja isti problem kao kad ne koristimo ključnu reč var, napraviće se globalna promenjiva.

Znači da će c biti globalno, a ne deo obj-a kao što smo možda očekivali.

ES6 je promenila Javaskriptu

Dolaskom EcmaScript 2015 (ES6) dobili smo potpuno novu Javaskriptu, sa ozbiljnim promenama od kojih su dve nove ključne reči let i const.

let i const su ključne reči za deklaraciju promenjivih koji rešavaju probleme var-a. Dodavanjem let-a i const-a Javaskripta je promenila svoju inicijalnu ideju definisanja oblasti, pošto nove ključne reči imaju oblast važenja na nivou bloka. Takođe promenjive se više ne podižu (hojstuju), već ostaju gde su deklarisane.

for (let i = 0; i < 17; i++) {
    // Neki kod
}
console.log(i); // Greška

Ovim smo rešili većinu problema, ali takođe ove reči treba gledati i sa strane optimizacije.

Optimizacija u deklaraciji

Možda je na prvi pogled vrlo čudno kako zamenom var-a let-om možemo da dobijemo bilo kakve dobitke u pogledu performansi. Ali ako zađemo malo dublje u rad Javaskripte u pozadini, možemo da vidimo i zašto.

Iako želim da napišem poseban članak kako radi Javaskript u pozadini, evo ukratko pregleda. Skupljač smeća (garbage colletor) je zadužen da se brine o memoriji, pošto ako stalno pravimo objekte i nikad ih ne brišemo kad tad će naša aplikacija da ostane bez memorije. Zato skupljač smeća kada "vidi" da se promenjiva više ne koristi on će da ukloni tu promenjivu i ono na šta ona pokazuje.

Pošto  var ima samo oblast važenja nad funkciom skupljač smeća može da očisti te promenjive samo ako su u funkciji inače su uvek globalne. Zato let i const koji imaju oblast važenja u odnosu na blok i biće obrisani kad se zatvori blok, pružaju optimizaciju.

var uslov = true

if (uslov) {
    var korsnik1 = { //... }
} else {
    var korsnik2 = { //... }
}

U ovom primeru korisnik1 i korisnik2 se nalaze u globalnoj oblasti i neće biti obrisani sve dok se ne završi program, dok ako ovo napišemo koristeći let ili const:

let uslov = true

if (uslov) {
    let korsnik1 = { //... }
} // korisnik1 Može da se obriše
else {
    let korsnik2 = { //... }
} // korisnik2 Može da se obriše

Zaključak

Možda ovaj članak deluje kao dug, ali postoji dosta problema i rešenja što se tiče oblasti definisanosti u Javaskripti. Potrebno je dobro vladati i znati koja će promenjiva gde da se javi kako bismo izbegli greške.

Možda ova priča zvuči kao nepotrebna zato što koristeći use strict, let i const nama su rešeni svi problemi. Na žalost to nije tako i ovaj članak je uvod za jedan dosta važniji a to je Šta je this u Javaskripti?

]]>
<![CDATA[Javaskript tajmeri i asinhroni problemi]]>https://ilic.ninja/asihroni-dogadaji-koji-se-ponavljaju/5b8f9c7fcb116e45fd4c2d3bFri, 05 Oct 2018 14:21:48 GMTAko malo prelistamo dokumentaciju, možemo naći funkciju setInterval() , koja se koristi za za periodično pozivanje neke druge funkcije. Sa druge strane, postoji funkcija setTimeout(), koja poziva neku drugu funkciju posle određenog vremena.

Primer (setInterval()):

function hello() {
  console.log('Sta radis?');
}

setInterval(hello, 1000);

Na svakih 1000 milisekundi (1 sekunda) će se pozvati funkcija hello(), koja će u konzoli ispisati 'Sta radis?'.

Primer (setTimeout()):

function hello() {
  console.log('Dobro dosao');
}

setTimeout(hello, 1000);

Posle 1000 milisekundi (1 sekunda) će se pozvati funkcija hello(), koja će u konzoli ispisati 'Dobro dosao'.

Iz opisa možemo zaključiti da se setInterval() koristi za događaje koji se periodično ponavljaju, dok se setTimeout()koristi za odlaganje nekog događaja, što je delimično tačno.

Ako setTimeout() poziva funkciju u kojoj je definisan (rekurzija) dobijamo isti efekat koji dobijamo i pomoću setInterval() funkcije.

function hello() {
  console.log('Sta radis?');
  setTimeout(hello(), 1000);
}

Da li ima razlike?

Iako u ovom primeru nema razlike, ona definitivno postoji. Razika će se pokazati u slučaju kada naša funkcija radi nešto vremenski zahtevno.

Ako funkcija, koja se izvršava, radi neki zahtevan posao ili jednostavno radi AJAX poziv, i ima duže vreme izvršavanja nego što je postavljeni interval, dolazi do zagušenja. setInterval() će probati da održi svoj interval, gde dolazimo do problema da se javlja dupli poziv iako ne treba. Taj problem možemo da rešimo ako umesto setInterval() iskoristimo setTimeout(). Ali prvo da vidimo na primeru kako se ponašaju.

let runs = 0;
let execTime = 0;
const startTime = new Date().getTime();

function hello() {
  if (++runs === 10) {
    clearInterval(timer);
  }

  const now = new Date().getTime();
  execTime = now - startTime;

  // Trosi neko vreme neko vreme
  for (let i = 0; i < 100000; i++) {
    document.querySelector('body > a');
  }
                             
  let exec = new Date().getTime() - now;
                             
  console.log(`#${runs} poziv je poceo u ${execTime}ms i trajao je ${exec}ms`);

};

const timer = setInterval(hello, 1000);

Pokretanjem gore navedenog koda, dobijamo sledeći izlaz:

#1 poziv je poceo u 1000ms i trajao je 529ms
#2 poziv je poceo u 2001ms i trajao je 566ms
#3 poziv je poceo u 3000ms i trajao je 554ms
#4 poziv je poceo u 4001ms i trajao je 553ms
#5 poziv je poceo u 5002ms i trajao je 546ms
#6 poziv je poceo u 6001ms i trajao je 640ms
#7 poziv je poceo u 7003ms i trajao je 730ms
#8 poziv je poceo u 8002ms i trajao je 680ms
#9 poziv je poceo u 9003ms i trajao je 607ms
#10 poziv je poceo u 10003ms i trajao je 623ms

Ovde nema nikakvog iznenađenja. Funkciji treba neko vreme da se izvrši ali setInterval() odžava svoj interval.

Ono što možemo da primetimo je da početak poziva funkcije nije toliko precizan (kasni 2-3 ms). To je iz razloga zato što tajmeri u Javaskripti nisu toliko pouzdani. Moguće je da se sinhronizuju sa Date objektom.

Ako zamenimo setInterval() sa setTimeout() dobijamo sledeći slučaj

let runs = 0;
let execTime = 0;
const startTime = new Date().getTime();

function hello() {

  const now = new Date().getTime();
  execTime = now - startTime;

  // Trosi neko vreme neko vreme
  for (var i = 0; i < 100000; i++) {
    document.querySelector('body > a');
  }

  console.log(`#${runs + 1} poziv je poceo u ${execTime}ms i trajao je ${new Date().getTime() - now}ms`);

  if (++runs < 10) {
    setTimeout(hello, 1000);
  }
};

setTimeout(hello, 1000);

Što će nam dati sledeći rezultat:

#1 poziv je poceo u 1002ms i trajao je 565ms
#2 poziv je poceo u 2570ms i trajao je 601ms
#3 poziv je poceo u 4173ms i trajao je 533ms
#4 poziv je poceo u 5709ms i trajao je 509ms
#5 poziv je poceo u 7219ms i trajao je 642ms
#6 poziv je poceo u 8863ms i trajao je 742ms
#7 poziv je poceo u 10607ms i trajao je 848ms
#8 poziv je poceo u 12455ms i trajao je 651ms
#9 poziv je poceo u 14107ms i trajao je 564ms
#10 poziv je poceo u 15673ms i trajao je 673ms

Ovde takođe nema nikakvog iznenađenja. Pošto već znamo da setTimeout() ne prati nikakav interval, već sačeka da se završi funcija onda čeka odrđeno vreme pre sledećeg poziva.

Ok, sve je ovo već poznato,ali koja je reazlika u "realnom" svetu:

Recimo da radimo neki file upload i da se obrada fajla radi na backend serveru. Mi uploadujemo fajl, a zatim moramo konstanto da proveravamo da li je obrada završena. Ako koristimo setInterval() da radimo GET zahtev na svaku sekundu, ono što se može desiti jeste da je server zauzet i ako server ne vrati odgovor u roku od našeg intervala (jedna sekunda), funkcija setInterval() neće mariti — u suštini, ona ni ne zna, time će da pošalje još jedan AJAX zahtev. Dok kod setTimeout() rekurzije, mi prvo sačekamo odgovor i ako treba nakon toga pošaljemo još jedan AJAX zahtev.

Haos

Kada setInterval() preskoči interval šta se desi? Haos!!!

Ono što se desi zavisi malo od brzine računara kao i od veb pregledača koji korisnik koristi. Postoje dve mogućnosti:

  1. Pravi se da ništa nije bilo i normalno nastavi sa intervalom.
  2. Kaže "nema veze" i započne potpuno novi interval, na osnovu onog pogrešnog.

Kao i sve u veb svetu, ponašanje je u zavisnosti od veb pregledača. Firefox je jedinsteven, zato što jedino on ima ovo prvo (1) ponašanje. Doduše ovo nije potpuno tačno, zapravo se ponaša kao kombinacija prvog i drugog, evo kako:
U slučaju da FF propusti samo jedan otkucaj onda će se ponašati na prvi (1) način, u slučaju da propusti više od jednog otkucaja, prebaciće se na drugi (2) način.

Svi ostali veb pregledači imaju drugo (2) ponašanje.

Zaključak

Ako Vam je imalo stalo do tajminga ne koristite setInterval() već iskoristite rekurziju i setTimeout(). Za nijansu je lakše koristiti setInterval() ali može dovoesti do grešaka i raznih problema koji nisu očigledni.

Početkom 2011. Firefox i Chrome su postavili minimalno vreme čekanja za setTimeout i setInterval na jednu sekundu kada se pokreće na kartici koja nije aktivna (u pozadini).
]]>
<![CDATA[Modeliranje CSS-a kao softverski inžinjer]]>https://ilic.ninja/modeliranje-css-a-kao-softverski-inzinjer/5b83c4529c625d0f9948b8c0Mon, 27 Aug 2018 09:52:57 GMTKorisnici često shvataju CSS kao nešto trivijalno i vrlo lagano, sto jeste istina na nekom nivou. Ali pravljenje velike, skalabilne aplikacije nije isto.

Uporediću ulogu developera koji pravi CSS i  softverskog inženjera koji pravi softver. Vrlo suludo zvuči ali postoji dosta sličnosti.

Objedinjeni jezik za modelovanje

UML (Unified Modeling Language) - objedinjeni vizuelni jezik za
poslovno i softversko modelovanje u svim fazama razvoja i za sve
tipove sistema, kao i za generalno modelovanje kojim se definišu
statičke strukture i dinamičko ponašanje.

Oni koji nisu upoznati sa terminom, ovo je osnovni korak svakog softverskog inženjera, vizuelno prikazivanje objektnog modela. Na žalost veb developeri ne potpadaju u kategoriju softverskih inženjera, tako da oni ne mogu da koriste ovaj UML dijagram? Pa nije baš tako.

Ono gde se softver i veb spajaju

Ono što je zajedničko za softver i sajt jeste da je potrebno planiranje. Zvuči smešno? Ovo zvuči smešno u oba sveta ako se radi o malim, ne skalabilnim aplikacijama.

Smešno bi bilo crtati UML dijagram za kalkulator koji sabira brojeve, više vremena bi se utrošilo u crtanje no u programiranje samog kalkulatora. To isto važi i za veb, neće niko da planira sajt koji ima 3-4 vrlo proste stranice i to je to.

Zašto planirati CSS

Ceo ovaj članak zvuči suludo ali kada otkucate 20 000 linija u jednom CSS fajlu, shvatićete da ste se i sami izgubili u svom kodu.

Ono što smo sigurno bar jednom uradili je slepo kopiranje. To izgleda nekako ovako:

Floatovi nemaju visinu? Aha, odemo do google-a, potražimo rešenje.
Našli smo rešenje clearfix, iskopiramo kod u sred koda.
Par meseci kasnije desi se isto, zaboravimo klasu clearfix, napravimo novo rešenje za floatove, sad u klasi cf.

Upravo smo napravili dupli kod, koji je odmah teži za održavanje. Videćemo kako da rešimo te probleme.

Dizajn je vrlo bitan

Lepo je kad neko ima inspiraciju odmah uskoči u HTML i CSS i počne da radi bez dizajna. Dizajn treba gledati kao pod-UML dijagram.

Dizajn kao pod-dijagram

UML dijagram treba konstruisati iz dizajna. Gde su jedinstveni stilovi, objekti (komponente), a polja objekta njeni pod elementi.

Evo jednog primera koji se često javlja, "avatar box".

Grupisanje komponenti

Kao što vidimo gore imamo tri različite komponente, ustvari, ove tri komponente su iste i možemo da ih obuhvatimo jednom klasom.

UML dijagram avatar komponente

Klase će se zvati .avatar-box, kao podelemente saržaće sliku, naslov i opis. Slika je sa leve strane, naslov i opis sa desne, to je normalan izgled.
U slučaju da želimo okrugli avatar, u imenu klase dodamo modifikator --okrugla. Klase sada izgledaju ovako .avatar-box .avatar-box--okrugla gde će u CSS-u .avatar__slika imati border-radius: 50%.

Isti taj princip primenimo za dobijanje trećeg primera, dodamo modifikator --blok. Klase sada izgledaju ovako .avatar-box .avatar-box--blok gde će u CSS-u .avatar__slika imati display: block i tekst će biti centriran.

Pokazali smo da dobro planiranje može da nam uštedi vreme i prostor.

Zaključak

Naučili smo da tri vizuelno različita ali strukturno slična elementa možemo spojiti u jedan i tako izbeći pisanje više klasa.
Šta više, za primer iznad mogli smo dodati modifikatore za veličinu tako da imamo avatar sa velikom, malom i srednjom slikom.

Moja preporuka je kada imate dizajn da dobro sagledate koje su to komponente i na koji ćete ih način struktuirati, ako su strukture slične, verovatno ih možete obuhvatiti jednom klasom.

]]>
<![CDATA[Kolekcije u Javi]]>https://ilic.ninja/kolekcije-u-javi/5b4f50379c625d0f9948b8a3Tue, 20 Feb 2018 13:44:16 GMTKoje su to kolekcije u javi i kakve su njihove razlike. Šta možemo da očekujemo od svake struktore i koja su njena svojstva.

Liste

uml

ArrayList

Deklaracija:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable  

Karakteristike:

  • Može da sadrži duplikate
  • Zadržava red upisivanja
  • Nije sinhronizovana
  • Ima nasumični operator pristupa
  • Ukratko, dinamički array

Funkcije:

  • void add(int index, Object element)
  • boolean add(Object o)
  • void clear()
  • int lastIndexOf(Object o)
  • Object[] toArray()
  • int indexOf(Object o)

Primer:

ArrayList<String> korisnici = new ArrayList();

korisnici.add("Marko");
korisnici.add("Pera");
korisnici.add("Mika");

for ( int i = 0; i < korisnici.size(); i++ ) {
    System.out.println( korisnici.get(i) );
}
// Iterator
Iterator it = korisnici.iterator();
while ( it.hasNext() ) {
    System.out.println( it.next() );
}

LinkedList

Deklaracija:

public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, Serializable  

Karakteristike:

  • Može da sadrži duplikate
  • Zadržava red upisivanja
  • Nije sinhronizovana
  • Može da se koristi kao stek ili red
  • Brža je od ArrayList zato što nema potrebe za shiftovanje elemenata.

Funkcije:

  • void add(int index, Object element)
  • void addFirst(Object o)
  • void addLast(Object o)
  • boolean add(Object o)
  • boolean contains(Object o)
  • boolean remove(Object o)
  • int indexOf(Object o)
  • int lastIndexOf(Object o)
  • Object getFirst()
  • Object getLast()

Primer:

LinkedList<String> korisnici = new LinkedList();

korisnici.add("Marko");
korisnici.add("Pera");
korisnici.add("Mika");

for ( int i = 0; i < korisnici.size(); i++ ) {
    System.out.println( korisnici.get(i) );
}

// Iterator
Iterator it = korisnici.iterator();
while ( it.hasNext() ) {
    System.out.println( it.next() );
}

Razlike

Ima tri vrsta listi, neke razlike:

  • ArrayList - je implementirana kao dinamički niz.
  • LinkedList - je implementirana kao dvostruka lančana lista, ima mnogo bolje performanse na dodavanju i brisanju elemenata, ali i loši performanse prilikom get() i set() metode.
  • Vector - kao ArrayList samo je sinhronizovana.

test-liste
Slika preuzeta sa: programcreek.com

Setovi

HashSet

Deklaracija:

public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, Serializable  

Karakteristike:

  • Ne može da sadrži duplikate
  • Elemente pamti pomoću mehanizma heširanje
  • Ne pamti redosled dodavanja
  • Moguće je pisati svoj hashCode

Funkcije:

  • boolean add(Object o)
  • boolean contains(Object o)
  • boolean remove(Object o)
  • void clear()

Primer:

HashSet<String> korisnici = new HashSet();

korisnici.add("Marko");
korisnici.add("Pera");
korisnici.add("Mika");
korisnici.add("Marko"); // Nece da se pojavi

// Iterator
Iterator it = korisnici.iterator();
while ( it.hasNext() ) {
    System.out.println( it.next() );
}

/*
Output:
    Pera
    Mika
    Marko
 */

Napomena:

  • Kad god se preklapa equals() metoda mora da se prekolopi i hashCode(). Važi i suprotno!
  • Koristiti ista polja za izračunavanje equals() za izračunavanje hashCode()!

TreeSet

Deklaracija:

public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, Serializable 

Karakteristike:

  • Ne može da sadrži duplikate, kao HashSet.
  • Elemente pamti pomoću mehanizma heširanje
  • Sortirana kolekcija, pamti u rastućem redosledu.

Funkcije:

  • boolean add(Object o)
  • Object first()
  • Object last()
  • boolean contains(Object o)
  • boolean remove(Object o)
  • void clear()

Primer:

TreeSet<String> korisnici = new TreeSet();

korisnici.add("Marko");
korisnici.add("Pera");
korisnici.add("Marko"); // Nece da se pojavi
korisnici.add("Aca");

// Iterator
Iterator it = korisnici.iterator();
while ( it.hasNext() ) {
    System.out.println( it.next() );
}

/*
Output:
    Aca
    Marko
    Pera
 */

Razlike

  • HashSet je znatno brža strukturna od TreeSet-a. Zato što pruža konstantno vreme osnovnim operacijama (add(), remove(),size() i contains()). Dok je TreeSet implementirano kao stablo samim tim su sve operacije log(n).
  • HashSet nije uređena kolekcija.
  • Obe kolekcije nisu thread-safe.
  • Generalno je brže da se elementi dodaju u HashSet i zatim da se prebaci u TreeSet za sortiranu kolekciju.

Mape

Mape sadrže dva polja ključ i vrednost. Mapa sadrži samo jedinstvene ključeve. Par ključ i vrednost se nazivaju entry.

TreeMap

Deklaracija:

public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, Serializable  

Karakteristike:

  • Ne može da sadrži duplikate.
  • Elemente pamti pomoću mehanizma heširanje
  • Sortirana kolekcija, sortira po ključu.

Funkcije:

  • Object put(K key, V value)
  • boolean containsKey(Object key)
  • boolean containsValue(Object value)
  • Object firstKey()
  • Object lastKey()
  • Object get(Object key)
  • Object remove(Object key)
  • Set entrySet()
  • int size()

Primer:

TreeMap<Integer, String> korisnici = new TreeMap();

korisnici.put(40, "Aca");
korisnici.put(20, "Pera");
korisnici.put(10, "Marko");
korisnici.put(30, "Marko"); // Pojavljuje se, ima jedinstven kljuc

for ( Map.Entry<Integer, String> entry : korisnici.entrySet() ) {
    System.out.println( entry.getKey() + " => " + entry.getValue() );
}

// Drugi nacin za iteraciju, iteracija po kljucevima
Iterator<Integer> itr = korisnici.keySet().iterator();

while (itr.hasNext()) {
    Integer key = itr.next(); // Ovo vraca kljuc (10, 20, 30...)
    String user = korisnici.get(key);
    System.out.println( key + " => " + user );
}

/*
Output:
    10 => Marko
    20 => Pera
    30 => Marko
    40 => Aca
 */

Razlike

Ima četiri tipa mapa, evo i neke razlike:

  • HashMap - je implementirana kao heš tabela, nije uređena kolekcija.
  • TreeMap - je implementirana kao crno-crveno stablo, uređena kolekcija po ključu.
  • LinkedHashMap - sadržava redosled upisivanja.
  • Hashtable - sinhronizovana HashMap-a.
]]>
<![CDATA[UML veze sa primerima]]>Objedinjeni jezik za modelovanje (eng. Unified Modeling Language) je standard u softverskom inženjerstvu za vizuelizaciju objektnog modela.

Asocijacija

Veza koja kaže da objekat sadrži referencu drugog objekta.

asocijacija

U ovom primeru znači da klasa Automibil sadži instancu (referencu) klase Putnik. Ovaj tip veze je slab. Objekti mogu da žive nezavisno od

]]>
https://ilic.ninja/uml-veze/5b4f50379c625d0f9948b8a2Wed, 31 Jan 2018 13:19:25 GMTObjedinjeni jezik za modelovanje (eng. Unified Modeling Language) je standard u softverskom inženjerstvu za vizuelizaciju objektnog modela.

Asocijacija

Veza koja kaže da objekat sadrži referencu drugog objekta.

asocijacija

U ovom primeru znači da klasa Automibil sadži instancu (referencu) klase Putnik. Ovaj tip veze je slab. Objekti mogu da žive nezavisno od veze.

Primer koda:

public class Automobil {
	private Putnik putnik;
	
  	public void setPutni(Putnik p) {
    	this.putnik = p;
  	}
	public Putnik getPutnik() { return putnik; }
}

Agregacija

Agregacija je slična asocijaciji, ustavri agregacija je specijalan slučaj asocijacije. Takođe ovaj tip veze je slab i objekti mogu da postoje nezavisno. Međutim ovde su objekti važan deo objekta koji ih agregira.

agregacija

U ovom primeru znači da klasa Automobil sadrži više instanci klase Guma.

Primer koda:

public class Automobil {
	private ArrayList<Guma> gume;

	public void setGuma(Guma g) {
		gume.add(g);
	}

	public ArrayList<Guma> getGume() {
		return gume;
	}
}

Dijaman na strani klase znači da ona agregira (sadrži) drugu klasu. Veza se kreira tako što se vuče od strane koju hoćemo da agregiramo do strane koja agregira.

Kompozicija

Kompozicija je takođe specijalan slučaj asocijacije. Dok su agregacija i asocijacija slabi tipovi, kompozicija je jak tip veze. Objekti klase koja je agregirana ne mogu postojati ako ne postoji objekat klase koja ih agregira.

kompozicija

U ovom primeru znači da se klasa Automobil sačinjena od Šasije. Ako se automobil obriše briše se i šasija zato što šasija bez automobila ne postoji. Automobil je dužan da napravi šasiju.

Primer koda:

public class Automobil {
	private Sasija sasija;

    public Automobil() {
    	this.sasija = new Sasija();
    }
}

Ovde je jaka veza ilustrovana tako što se prilikom konstrukcije objekta, kreira i šasija koja se vezuje za automobil.

Generalizacija

Generalizacija ili specijalizacija je verovatno najjednostavniji tip veze u UML-u, zato što generalizacija predstavlja ono što je u objektno orijentisanom programiranju poznato kao nasleđivanje.

generalizacija

U ovom primeru klase Limuzina, Karavan, Hatchback nasleđuju klasu Automobil

Primer koda:

public class Limuzina extends Automobil {}
public class Karavan extends Automobil {}
public class Hatchback extends Automobil {}

Realizacija

Realizacija ili apstraktna generalizacija je isto što i generalicazija samo što se nasleđuje apstraktna klasa (interfejs).

realizacija

U ovom primeru IFabrika je interfejs i implementiraju ga klase Fabrika Zastava i Fabrika Fiat.

Primer koda:

public interface IFabrika {}

public class Fabrika_Zastava implements IFabrika {}
public class Fabrika_Fiat implements IFabrika {}
]]>
<![CDATA[CSS Tooltip]]>https://ilic.ninja/css-tooltip/5b4f50379c625d0f9948b8a0Thu, 26 Oct 2017 16:25:18 GMTPogledaćemo na koji način možemo da napravimo tooltip komponentu samo pomoću CSS tehnologije. Obradićemo i moguće probleme ovog pristupa, kao i na koji način ih rešiti.

Šta je tooltip?

Tooltip ili infotip ili hint je komponenta koja u kombinaciji sa kursorom služi da nam pruži dodatne informacije.

Kada korisnik kurosorm pređe preko objekta, pojavljuje se tooltip sa informacijama o stavci preko koje je prešao kursorom.

Treba imati na umu da tooltip nije isto što i popover. Popover može da sadrži i multimedijalni sadržaj, dok tooltip samo sadrži tekst.

Ugrađeni tooltip

Veb pregledač ima već ugrađeni tooltip. On se postavlja tako što postavite atribut title na neki element.

<span title="Ovo je tooltip">Pređi preko mene</span>
Pređi preko mene

Nažalost, ovaj tooltip nije moguće stilski promeniti, pa je zato pogodno napraviti naš tooltip.

Verzija 1

Ovo je verzija koju najčešće viđamo kao rešenje za tooltip pomoću CSS-a

U HTML-u nam je tooltip predstavljen preko jednog elementa, gde tooltip kontrolišemo preko atributa.

<span data-tooltip="Ovo je tooltip">Pređi preko mene</span>

Korisnički navedeni atributi moraju da počinju sa data-

Klasa nam uopšte nije potrebna već ćemo selekciju vršiti preko selektora atributa.

[data-tooltip] {
    position: relative;
  	cursor: pointer;
}

Selektujemo sve elemente koji imaju atribut data-tooltip. Dodelićemo mu poziciju koja će biti relative zato što će unutar sebe imati apsolutne elemente. Takođe smo dodali opciju cursor kako bismo naznačili korisniku kada pređe kursorom preko elementa da postoji neka dodatna informacija.

Unutar tooltip elementa moramo da dodamo pseudo element :before, koji će sadržati sam tekst tooltipa.

On treba da bude apsolutne pozicije zato što će plutati iznad teksta. Sadržaj ćemo uzeti iz samog atributa, pomoću attr() 'funkcije' u CSS-u.

[data-tooltip]::before {
    content: attr(data-tooltip);
    
    position: absolute;
    bottom: 100%; /* Gurnemo ga do vrha */
    left: 50%; /* Stavimo na pola */
    transform: translate(-50%, -4px); /* Centriraj vertikalno i pomeri na gore 4px */
    
    padding: .5em 1em;
    border-radius: 4px;
    background-color: rgba(20, 20, 20, 0.9);
    color: #fff;
    
  
    white-space: nowrap;
    z-index: 7;
}

Ovim smo sada dobili jedan vrlo lep tooltip.

Pređi preko mene

Preostalo nam je da napravimo animaciju. Iskoristićemo dva svojstva u CSS-u visibility i opacity.

Preko svojstva visibility ćamo sakriti tooltip tako da ne smeta elementima iznad, zato što ako koristimo samo svojstvo opacity onda ako postoji element na mestu našeg tooltipa koji treba da se pojavi, korisnik neće moći da dođe do tog elementa, pošto se tooltip nalazi ispred njega. opacity svojstvo će nam služiti samo radi animacije.

[data-tooltip]::before {
    visibility: hidden;
    opacity: 0;
    transition: opacity .3s ease-in-out;
}

Dodamo :hover događaj, kad se pređe preko [data-tooltip] da se pojavi :before.

[data-tooltip]:hover::before {
  visibility: visible;
  opacity: 1;
}
Pređi preko mene

Dobili smo traženi rezultat.

Dodatne opcije

Pozicija

Ovo je najprostiji primer gde se tooltip uvek pojavljuje iznad samog sadržaja ali lako možemo nadograditi ovaj tooltip da se sadržaj pojavljuje sa bilo koje strane.

Ja ću napraviti samo jedan primer, vrlo je lako uraditi za ostale pozicije.

Prvo moramo uvesti dodatni atribut koji određuje poziciju, na primer data-tooltip-position.

<span data-tooltip="Ovo je tooltip" data-tooltip-position="desno">Pređi preko mene</span>

U CSS-u izaberemo element koji ima data-tooltip-position i čija je vrednost desno.

[data-tooltip-position="desno"]:before {
  bottom: 50%; /* Pola visine */
  left: 100%; /* Gurnemo skroz desno */
  transform: translate(4px, 50%); /* Centriraj horizontalno i pomeri na desno 4px */
}

Ostale

Moguće je dodati još neke opcije kao što su:

  • Dodavanje strelice
  • Različite boje (uspešno, upozorenje, info, opasnost)
  • Različite širine
  • Različite animacije

Nedostaci

Ovaj pristup iako vrlo jednostavan i elegantan ima jedan nedostatak, a to je da ako njegov roditelj ima overflow: hidden tooltip se neće videti.

<div class="overflow">
  <span data-tooltip="Ovo je tooltip">Pređi preko mene</span>
</div>
.overflow {
  overflow: hidden;
  border: 1px solid Tomato;
  padding: 20px;
}
Pređi preko mene

Ovaj problem sa ovakvom HTML struktorom nije moguće rešiti.

Verzija 1.1

Ova verzija nije konkretno rešenje već će nam pomoći da dođemo do krajnjeg rešenja.

Kako bi implementirali ovu verziju potrebno je da izmenimo malo strukturu.

<span>Pređi preko mene
	<span class="tooltip">Ovo je tooltip</span>
</span>

.tooltip će biti absolutno pozicioniran.

.tooltip {
    position: absolute;
  	background-color: DodgerBlue;
}

Sada da probamo naš primer:

<div class="overflow">  
	<span>Pređi preko mene
      <span class="tooltip">Ovo je tooltip</span>
  	</span>
</div>
Pređi preko mene Ovo je tooltip

Hmmm, nekako čudno tooltip nije više isečen, o čemu se radi?

Ovde je tooltip relativan u odnosu na neki drugi element a ne na onaj koji ga ograničava.

Nedostaci

Jedan veliki nedostatak ovog pristupa, koji ga ustvari čini ne primenljivim, je to što u slučaju da koristimo svojstva za pozicioniranje (top, right, left i bottom) dobićemo potpuno neočekivane rezultate, zato što je tooltip relativan na neki drugi element (često body).

Verzija 2.0 (Final)

Probaću da modifikujem verziju 1.1 i napravim nešto funkcionalno. Način na koji možemo da rešimo problem pomeranja samog tooltipa ako nije relativan na ono što želimo jeste da ga obmotamo jednim apsolutnim divom i sam tooltip pomeramo sada relativno. Možda zvuči komplikovano, ali uopšte nije ukoliko se razume šta se ustvari dešava.

Postaviću novu strukturu:

<span>Pređi preko mene!
  <span class="tooltip-wrapper">
    <span class="tooltip">Ovo je tooltip</span>
  </span>
</span>

.tooltip-wrapper će imati istu ulogu kao .tooltip u verziji 1.1, biće samo apsolutno pozicioniran.

.tooltip-wrapper {
  position: absolute;
}

Dok će unutar njega postojati još jedan element koji će biti relativan, upravo zbog pomenutog problema nemogućnosti pomeranja.

.tooltip {
    position: relative;
    right: 100%; /* Sada pomeramo tooltip */
    bottom: 2em;

    padding: .5em 1em;
    border-radius: 4px;
    background-color: rgba(20, 20, 20, 0.9);
    color: #fff;
  }

Dobijemo upravo ono što smo tražili

Pređi preko mene! Ovo je tooltip

Dodaću na .tooltip-wrapper-u plavu boju kako bismo lakše shvatili gde se on ustvari nalazi.

Pređi preko mene! Ovo je tooltip

Nedostaci

Ovaj pristup, iako pruža rešenje, meni lično nije sintaksno lep, struktura je komplikovana.

Verzija 2.1 (Final final)

U cilju poboljšanja verzije 2.0 napravio sam verziju 2.1 koja ima malo uprošćenu strukturu. Rešenje je kombinacija rešenja 2.0 i 1.0.

<span>Pređi preko mene!
  <span class="tooltip-wrapper" data-tooltip="Ovo je tooltip"></span>
</span>

Kao u verziji 1.0 ću iskoristiti pseudo elemente da dodam sam tooltip, samo što ga sada dodajem u naš .tooltip-wrapper koji je apsolutan.

.tooltip-wrapper {
    position: absolute;
}

.tooltip-wrapper::before {
    content: attr(data-tooltip); /* Kao u verziji 1.0 */
    position: relative;
    right: 100%;
    bottom: 2em;

    padding: .5em 1em;
    border-radius: 4px;
    background-color: rgba(20, 20, 20, 0.9);
    color: #fff;
}

Kranji rezultat:

Pređi preko mene!

Zaključak

Prošli smo kroz par načina pravljenja tooltip-a u CSS-u, neki su bili dobri, neki loši, ali smo kroz svaki valjda naučili nešto.

Tooltip je, takođe, moguće napraviti preko javaskripte, pisaću i o tome vrlo uskoro, sa željom da uporedim oba pristupa što se tiče preformansi.

]]>
<![CDATA[CSS Selektor matematika]]>Da li Vam se dešavalo da ne možete da preklopite neki stil koji ste napisali u prethodnom delu koda? Iako bi ovo trebalo da bude prilično jednostavno zato što CSS funkcioniše kaskadno - primenjuje se ono poslednje napisano. Nažalost ovo nije uvek istina, a najbolje ću pokazati uz pomoć primera.

]]>
https://ilic.ninja/css-selektor-matematika/5b4f50379c625d0f9948b89fMon, 09 Oct 2017 10:34:19 GMTDa li Vam se dešavalo da ne možete da preklopite neki stil koji ste napisali u prethodnom delu koda? Iako bi ovo trebalo da bude prilično jednostavno zato što CSS funkcioniše kaskadno - primenjuje se ono poslednje napisano. Nažalost ovo nije uvek istina, a najbolje ću pokazati uz pomoć primera.

Evo primera jedne proste navigacije:

<ul id="nav">
    <li>Početna</li>
    <li>O meni</li>
    <li>Kontakt</li>
</ul>

Tokom razvoja aplikacije želite da dodate "aktiv" klasu, koja će da naznači krajnjem korisniku na kojoj se stranici nalazi.

<ul id="nav">
    <li class="trenutna">Početna</li>
    <li>O meni</li>
    <li>Kontakt</li>
</ul>

Naravno, to dodate u CSS, recimo ovako:

.trenutna {
    color: #FF0000;
    text-decoration: underline;
}

... Osvežite stranicu ... ne radi... ???

Nervnozno prolazite kroz CSS gore-dole i uporno ne primećujete ovo:

ul#nav li {
    color: #343F44;
    text-decoration: none;
}

Ovo izgleda normalno zato što se nalazi pre našeg dodatka .trenutna. Ali na žalost to nije tako jednostavno.

Računanje preciznosti

Svaka CSS definicija ima svoju težinu koja je određena brojem tipova selektora unutar definicije.

Tipovi selektora

  1. Selektori elementa (h1, a, h5, p, body,...)
  2. Selektori klasa (.klasa)
  3. Selektori ID-a (#neki-id)
  4. Inline selektori (style="color: red;")

Svaki od njih dodaje određenu težinu definiciji selektora:

  1. Elementi - 1
  2. Klase, pseudo, atributi - 10
  3. ID-evi - 100
  4. Inline selektori - 1000

Ako postoji ovakav selektor:

.clasa > li > a možemo lako da izračunamo težinu:
Jedna klasa = 10
Dva elementa = 1 + 1
Ukupna težina = 12

Malo prostiji način računanja jeste da zamislimo četvorocifreni broj, gde su jedinice broj html elemenata u selektoru, desetice broj klasa u selektoru, stotice broj ID-jeva u selektoru i hiljade ako postoji inline stil.

css-calculator_web

Recimo imamo ovakav selektor:
nav#nav > ul.lista > li.active > a

Težina je:
css-calculator-2_web-1

Pseudo selektori

Pseudo selektori takođe utiču na težinu selektora. Oni se ubrajaju u desetice kao i klase.

.nav li:first-child

Težina ovog selektora je: 0021

Pseudo selektore ne treba pomešati sa pseudo elementima!

Pseudo elementi

Pseudo elementi takođe utiču na težinu selektora, ali kao element. Znači dodaje se kao jedinica.

.lista > .element::before

Težina ovog selektora je: 0021

:not() selektor

:not() selektor je izuzetak. On se ne računa kao selektor, već se računa ono unutar njega.

.lista:not()

Težina ovog selektora je: 0010

Međutim, ako stavimo klasu, id ili element unutar :not() selektora, ti elementi će se računati.

.lista:not(.numerisana)

Težina ovog selektora je: 0020

!important deklaracija

!important igra vrlo važnu ulogu ali ne na nivou selektora, nego na nivou vrednosti.

.nav { color: #F00 !important; }

U ovom primeru color ima najveću moguću vrednost i nije je moguće preklopiti niti jednom težinom selektora.

Način na koji možemo da predefinišemo !important deklaraciju je:

  1. Korišćenjem !important deklaracije uz veću težinu selektora.
.nav li { color: red !important; } /* ovo vazi */
.nav { color: green !important; }
  1. Definisanjem iste !important deklaracije samo kasnije
.nav { color: red !important; }
.nav { color: green !important; }
]]>
<![CDATA[CSS pametni selektor atributa]]>https://ilic.ninja/css-pametni-selektor-atributa/5b4f50379c625d0f9948b89eFri, 06 Oct 2017 11:23:52 GMTHoću da iskoristim moć CSS selektora kako bih napravio pametan CSS. Ideja je sleće:

Želim da napravim "pametne" linkove koji će pokazivati da li je protokol http ili https. Ako je https da promeni boju linka u zeleno.

Pristup problemu

Razmišljamo kako je ovo moguće? Ok, hajde da krenemo od rešenja u javaskripti.

Prvo rešenje koje sam smislio je da koristimo regex, prođemo kroz tekst i nadjemo linkove.

var links = document.getElementsByTagName('a'); // Nadjemo sve linkove
var href;
for (var i = 0; i < links.length; i++){ // Moguce je koristis for..of za nodelistu
  href = links[i].getAttribute('href'); // Izvučemo href atribut
  if(href.match(/https:/)){ //proverimo da li atribut počinje sa https
  	links[i].className += " siguran"; // ako jeste onda dodamo klasu siguran
  }
}

Ovo je jedan od načina za rešavanje ovakvog problem. Naravno u jQuery-u bi ovo bilo bar dva puta lakše.

Način na koji smo rešili ovaj problem u JS-u je tako što smo našli (selektovali) sve linkove čiji atribut href počinje sa https.
Ovo je, ustvari, moguće uraditi i u CSS-u.

Selektor atributa

U CSS-u postoji selektor atributa i on postoji još od verzije CSS2. Tako da je ovaj selektor podržan na svim većim pregledačima.

Kako se pišu atribut selektori

Atribut selektori se pišu sa kockastim zagradam, ako ste ikad koristili jQuery i selektovali atribut, verovatno ste upoznati.

Između kockastih zagrada [ ] navedemo ime atributa,a moguće je navesti i vrednost

[attr]{}

Selektuje elemente koji imaju atribut attr.

<div attr="test">Imam atribut attr.</div>

Možemo da selektujemo atribute sa određenom vrednošću

[attr="novi"]{} /* moguće je napisati bez navodnika */
<div attr="test">Ja nisam selektovan :(</div>
<div attr="novi">Ja sam selektovan :D</div>

Ok, ovo sve je lepo ali kako će nam ovo pomoći? U ovoj formi ne još, ali uvešćemo džokerske znakove.

Džoker znakovi

Džoker znakovi su specijalni znakovi koji stoje na mestu nepoznatih znakova u tekstualnoj vrednosti i prikladni su za pronalaženje više stavki sa sličnim, ali ne identičnim podacima.

Dodavanjem džoker znakova u selektor atributa možemo dobiti pametan selektor

Atribut sadži

Dodavanjem * ispred znaka =, selektujemo sve elemente sa atributom koji sadrži datu reč.

Ovo poklapa cele reči i parcijalne (u sklopu drugih reči)!

[attr*="beograd"]{}
[attr*="beli grad"]{}
<div attr="beli grad">Ja sam selektovan zato sto imam reč grad :D</div>
<div attr="beograd">Ja sam selektovan zato sto unutar moje reči beo(grad) ima grad :D</div>

Atribut liste vrdnosti koja sadži

Dodavanjem ~ ispred znaka =, selektujemo sve elemente sa atributom koji u svojoj listi vrednosti sadrži traženi niz karaktera.

[attr~="grad"]{}
<div attr="beli grad">Ja sam selektovan zato sto u mojoj listi atributa imam grad :D</div>
<div attr="lep beograd">Ja nisam selektovan zato što u listi nemam grad kako posebnu reč :(</div>

Atribut koji se završava sa

Dodavanjem $ ispred znaka =, selektujemo sve elemente sa atributom cija se vrednost završava traženim nizom karaktera.

[attr$="ad"]{}
<div attr="beli">Ja nisam selektovan zato što mi se vrednost atributa ne završava sa AD :(</div>
<div attr="lep beograd">Ja sam selektovan zato što mi se vrednost atribut završava sa AD :D</div>

Atribut koji počinje sa

Dodavanjem ^ ispred znaka jednako, selektujemo sve elemente sa atributom čija vrednost počinje datim nizom karaktera.

Ovo je upravo ono što nam treba za rešavanje problema.

[attr^="lep"]{}
<div attr="beli lepi">Ja nisam selektovan zato što mi se vrednost atributa ne počinje sa LEP :(</div>
<div attr="lep beograd">Ja sam selektovan zato što mi se vrednost atribut počinje sa LEP :D</div>

Pametni atribut selektor

Sada kad smo naučili džoker znakove unutar selektora atributa, možemo da rešimo prethodni primer.

Moramo da selektujemo sve linkove koji počinju sa https.

a[href^="https"]{
	color: green;
}
<a href="https://google.com">Ja sam zelen :D</a>
<a href="http://google.com">Ja nisam siguran :(</a>

Ovo je jedan od korisnih slučajeva, možemo preko pseudo selektora da dodamo ikonicu katanca na sigurne linkove i mnogo još.

Pokazaću vam još jedan koristan primer, iako ih ima mnogo.

Pre par godina sam hteo da napravim da korisnik po izgledu dugmeta zna koji tip fajla skida. Prvobitna ideja mi je bila
da se to odradi preko php-a ali sam se setio dobrog starog selektora atributa.

Evo kako je to izgledalo:

a[href$=".zip"] { color: #D766CE; }
a[href$=".pdf"] { color: #E41D24 }
a[href$=".xls"], a[href$=".xlsx"] { color: #207347 }
a[href$=".doc"], a[href$=".docx"] { color: #2A5699 }
<a href="http://primer.com/materijali/kako_biti_pametan.pdf">Crvena slova</a>
<a href="http://primer.com/materijali/lista_zelja.xlsx">Zelena slova</a>
...

Zaključak

Videli smo kako je moguće preko CSS-a rešiti neke dinamične probleme, za koje smo se ranije uvek okretali ka javaskripti ili drugima.
Ja sam ovde samo prošao kroz dva korisna primera, a ima ih mnogo, i sâm sam smišljao dosta korisnih primena.

Probajte sami i igrajte sa selektorima.

]]>