1<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><title>Home - smnh</title><meta name="viewport" content="width=device-width, initial-scale=1"/><meta property="og:site_name" content="smnh"/><meta property="og:url" content="https://smnh.me/"/><meta property="og:title" content="Home"/><meta property="og:description" content="Simon Hanukaev's blog about software development"/><meta property="fb:admins" content="597874473"/><meta property="fb:app_id" content="763880877072491"/><meta name="description" content="Simon Hanukaev's blog about software development"/><meta name="author" content="Simon Hanukaev"/><link rel="icon" href="/favicon.ico"/><link rel="canonical" href="https://smnh.me/"/><meta name="next-head-count" content="13"/><link rel="preconnect" href="https://fonts.googleapis.com"/><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="true"/><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /><link as='style' href='https://d33wubrfki0l68.cloudfront.net/css/c6151a41b98458f09d0cfa9c150059a89f70039a/_next/static/css/39eb24a1391c39f6.css' rel='preload'/><link data-n-g href='https://d33wubrfki0l68.cloudfront.net/css/c6151a41b98458f09d0cfa9c150059a89f70039a/_next/static/css/39eb24a1391c39f6.css' rel='stylesheet'/><noscript data-n-css=""></noscript>
1<script defer="" nomodule="" src="/_next/static/chunks/polyfills-5cd94c89d3acac5f.js"></script>
1<script src="/_next/static/chunks/webpack-aedbed00aeb21485.js" defer=""></script>
1<script src="/_next/static/chunks/framework-ff8c868125feccdc.js" defer=""></script>
1<script src="/_next/static/chunks/main-216a4a75cad72552.js" defer=""></script>
1<script src="/_next/static/chunks/pages/_app-59d490c7005e2823.js" defer=""></script>
1<script src="/_next/static/chunks/321-85744d7f579a206f.js" defer=""></script>
1<script src="/_next/static/chunks/966-2bd8a9243cb79a8f.js" defer=""></script>
1<script src="/_next/static/chunks/825-5132f01328802466.js" defer=""></script>
1<script src="/_next/static/chunks/982-6de7bb9a35c5009c.js" defer=""></script>
1<script src="/_next/static/chunks/pages/index-9ec9bcf11fe471fe.js" defer=""></script>
1<script src="/_next/static/DUIJVVroaEOa-CVSMF9kL/_buildManifest.js" defer=""></script>
1<script src="/_next/static/DUIJVVroaEOa-CVSMF9kL/_ssgManifest.js" defer=""></script>
1<script src="/_next/static/DUIJVVroaEOa-CVSMF9kL/_middlewareManifest.js" defer=""></script>
1<style data-href="https://fonts.googleapis.com/css2?family=Coustard&family=Lora:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&display=swap">@font-face{font-family:'Coustard';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/coustard/v16/3XFpErgg3YsZ5fqUU9UM.woff) format('woff')}@font-face{font-family:'Lora';font-style:italic;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QI8MX1D_JOuMw_hLdO6T2wV9KnW-MoFkqs.woff) format('woff')}@font-face{font-family:'Lora';font-style:italic;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QI8MX1D_JOuMw_hLdO6T2wV9KnW-PgFkqs.woff) format('woff')}@font-face{font-family:'Lora';font-style:italic;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QI8MX1D_JOuMw_hLdO6T2wV9KnW-BQCkqs.woff) format('woff')}@font-face{font-family:'Lora';font-style:italic;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QI8MX1D_JOuMw_hLdO6T2wV9KnW-C0Ckqs.woff) format('woff')}@font-face{font-family:'Lora';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QI6MX1D_JOuGQbT0gvTJPa787weuyJF.woff) format('woff')}@font-face{font-family:'Lora';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QI6MX1D_JOuGQbT0gvTJPa787wsuyJF.woff) format('woff')}@font-face{font-family:'Lora';font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QI6MX1D_JOuGQbT0gvTJPa787zAvCJF.woff) format('woff')}@font-face{font-family:'Lora';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QI6MX1D_JOuGQbT0gvTJPa787z5vCJF.woff) format('woff')}@font-face{font-family:'Coustard';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/coustard/v16/3XFpErgg3YsZ5fqUU-UIt2TMR_Tc.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Lora';font-style:italic;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIhMX1D_JOuMw_LLPtLtfOm84TX.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Lora';font-style:italic;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIhMX1D_JOuMw_LJftLtfOm84TX.woff2) format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Lora';font-style:italic;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIhMX1D_JOuMw_LLvtLtfOm84TX.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Lora';font-style:italic;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIhMX1D_JOuMw_LL_tLtfOm84TX.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Lora';font-style:italic;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIhMX1D_JOuMw_LIftLtfOm8w.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Lora';font-style:italic;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIhMX1D_JOuMw_LLPtLtfOm84TX.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Lora';font-style:italic;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIhMX1D_JOuMw_LJftLtfOm84TX.woff2) format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Lora';font-style:italic;font-weight:500;font-display:swap;
1src:url(https://fonts.gstatic.com/s/lora/v26/0QIhMX1D_JOuMw_LLvtLtfOm84TX.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Lora';font-style:italic;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIhMX1D_JOuMw_LL_tLtfOm84TX.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Lora';font-style:italic;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIhMX1D_JOuMw_LIftLtfOm8w.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Lora';font-style:italic;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIhMX1D_JOuMw_LLPtLtfOm84TX.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Lora';font-style:italic;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIhMX1D_JOuMw_LJftLtfOm84TX.woff2) format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Lora';font-style:italic;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIhMX1D_JOuMw_LLvtLtfOm84TX.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Lora';font-style:italic;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIhMX1D_JOuMw_LL_tLtfOm84TX.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Lora';font-style:italic;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIhMX1D_JOuMw_LIftLtfOm8w.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Lora';font-style:italic;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIhMX1D_JOuMw_LLPtLtfOm84TX.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Lora';font-style:italic;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIhMX1D_JOuMw_LJftLtfOm84TX.woff2) format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Lora';font-style:italic;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIhMX1D_JOuMw_LLvtLtfOm84TX.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Lora';font-style:italic;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIhMX1D_JOuMw_LL_tLtfOm84TX.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Lora';font-style:italic;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIhMX1D_JOuMw_LIftLtfOm8w.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Lora';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIvMX1D_JOuMwf7I_FMl_GW8g.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Lora';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIvMX1D_JOuMw77I_FMl_GW8g.woff2) format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Lora';font-style:normal;font-weight:400;font-display:swap;
1src:url(https://fonts.gstatic.com/s/lora/v26/0QIvMX1D_JOuMwX7I_FMl_GW8g.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Lora';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIvMX1D_JOuMwT7I_FMl_GW8g.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Lora';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIvMX1D_JOuMwr7I_FMl_E.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Lora';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIvMX1D_JOuMwf7I_FMl_GW8g.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Lora';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIvMX1D_JOuMw77I_FMl_GW8g.woff2) format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Lora';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIvMX1D_JOuMwX7I_FMl_GW8g.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Lora';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIvMX1D_JOuMwT7I_FMl_GW8g.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Lora';font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIvMX1D_JOuMwr7I_FMl_E.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Lora';font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIvMX1D_JOuMwf7I_FMl_GW8g.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Lora';font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIvMX1D_JOuMw77I_FMl_GW8g.woff2) format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Lora';font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIvMX1D_JOuMwX7I_FMl_GW8g.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Lora';font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIvMX1D_JOuMwT7I_FMl_GW8g.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Lora';font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIvMX1D_JOuMwr7I_FMl_E.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Lora';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIvMX1D_JOuMwf7I_FMl_GW8g.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Lora';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIvMX1D_JOuMw77I_FMl_GW8g.woff2) format('woff2');unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Lora';font-style:normal;font-weight:700;font-display:swap;
1src:url(https://fonts.gstatic.com/s/lora/v26/0QIvMX1D_JOuMwX7I_FMl_GW8g.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Lora';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIvMX1D_JOuMwT7I_FMl_GW8g.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Lora';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/lora/v26/0QIvMX1D_JOuMwr7I_FMl_E.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}</style></head><body><div id="__next" data-reactroot=""><div class="bg-white"><header class="pb-12 font-header"><div class="wide-container"><div class="py-5 text-center"><a href="/"><div class="inline-block p-2 cursor-pointer"><div class="text-2xl border-b border-dotted border-theme-cc"><span class="text-theme-primary">s</span><span class="text-theme-primary">m</span><span class="text-theme-primary">n</span><span class="text-theme-primary">h</span></div></div></a></div><nav class="border-b border-t border-theme-ee"><ul class="flex flex-row justify-center items-center text-sm h-12"><li class="px-4"><a class="text-theme-33 hover:text-theme-33 hover:border-b hover:border-dotted hover:border-theme-cc" href="/">Home</a></li><li class="px-4"><a class="text-theme-33 hover:text-theme-33 hover:border-b hover:border-dotted hover:border-theme-cc" href="/about">About</a></li></ul></nav></div></header><main class="pb-16"><div class="wide-container"><div class="content-container"><div class="pt-5 pb-10"><h1 class="text-center font-header leading-normal text-3xl mb-3"><a class="text-theme-33 hover:text-black hover:bg-theme-f7" href="/extending-typescript-interfaces-and-type-aliases-with-common-properties">Extending TypeScript Interfaces and Type Aliases with common properties</a></h1><div class="text-center text-theme-66 text-smaller pt-2 pb-4 tracking-widest">posted <time dateTime="2013-02-02T10:00:00+00:00">on <!-- -->December 21, 2022</time></div><div class="pt-1"><p>In this article, I want to discuss what happens when extending TypeScript Interfaces and intersecting Type Aliases that have common properties of different types.</p><figure class="image-container"><span style="box-sizing:border-box;display:inline-block;overflow:hidden;width:initial;height:initial;background:none;opacity:1;border:0;margin:0;padding:0;position:relative;max-width:100%"><span style="box-sizing:border-box;display:block;width:initial;height:initial;background:none;opacity:1;border:0;margin:0;padding:0;max-width:100%"><img style="display:block;max-width:100%;width:initial;height:initial;background:none;opacity:1;border:0;margin:0;padding:0" alt="" aria-hidden="true" src="data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20version=%271.1%27%20width=%27768%27%20height=%27576%27/%3e"/></span><img alt="TypeScript type intersection diagram" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" decoding="async" data-nimg="intrinsic" style="position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%;filter:blur(20px);background-size:cover;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAMCAIAAADkharWAAAACXBIWXMAABcSAAAXEgFnn9JSAAABoUlEQVQokW2QsUsbURzH7w9w0yEUgou4Bdql4OKiUUyFdgpBHIQDSYZ0SOka5YkECifkLkM6vBZ6gxKIiMng05o3XYY3vQvmRcyL6CHcIb6lb3pLr+SuQ0nzmX8/Pt/vV/O8p0azhdDPu+GQscHj44NSKoxQSp21Lpun567r3vSZ67pCCI2xwezs3Kvk4vrm9uu3G+DgS8+lJxGc89X0mqbNLK9ks1u75b3DER9q96PRkWHUv35vNNsXCF1dXUv5izGGMRZCmGa1XN6v1b5ddzoYY865JoRot9v1et22f0gplVJCCKWUlDKchhaGoW3buq7ncjkAgGEY+XweQug4DsaYEOI4XcfpUkoJIVLKsQFCaFlWfIEQ8jyPEEIpDXxfRcQDxP6xwTCMYrGoRwAAEEJxhyinjTG2IzDGfyNRSiuVSiaTAQBACE2zallWqfQplUql02mEUOD7nucFvj9+iKWxMSYInhkb9Ps9IV7iGJOlJyh+/JycX3j3Yef4pBGGv6esNEGhkE8kEm+W3lfN2vRZ/0UpRSlljN0Objnn/z/8AT/jwKl9VN6hAAAAAElFTkSuQmCC");background-position:0% 0%"/><noscript><img alt="TypeScript type intersection diagram" srcSet="https://d33wubrfki0l68.cloudfront.net/6999bf8c29de0dc39039a2913d2ae2662ae025b1/1e3b9/resized-images/extending-typescript-interfaces-and-type-aliases-with-common-properties/typescript-type-aliases-intersection-diagram-sharp-768x576.webp 1x, https://d33wubrfki0l68.cloudfront.net/6999bf8c29de0dc39039a2913d2ae2662ae025b1/1e3b9/resized-images/extending-typescript-interfaces-and-type-aliases-with-common-properties/typescript-type-aliases-intersection-diagram-sharp-768x576.webp 2x" src="https://d33wubrfki0l68.cloudfront.net/6999bf8c29de0dc39039a2913d2ae2662ae025b1/1e3b9/resized-images/extending-typescript-interfaces-and-type-aliases-with-common-properties/typescript-type-aliases-intersection-diagram-sharp-768x576.webp" decoding="async" data-nimg="intrinsic" style="position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%" loading="lazy"/></noscript></span></figure></div><div class="flex justify-center mt-8 pb-5"><a class="py-1.5 px-14 mx-auto text-center text-theme-44 hover:text-black hover:bg-theme-f7 text-sm border-b border-dotted border-theme-cc tracking-widest " href="/extending-typescript-interfaces-and-type-aliases-with-common-properties">Continue reading â</a></div></div><div class="pt-5 pb-10"><h1 class="text-center font-header leading-normal text-3xl mb-3"><a class="text-theme-33 hover:text-black hover:bg-theme-f7" href="/add-class-to-body-tag-in-nextjs">How to add a class to the body tag in Next.js</a></h1><div class="text-center text-theme-66 text-smaller pt-2 pb-4 tracking-widest">posted <time dateTime="2013-02-02T10:00:00+00:00">on <!-- -->July 31, 2021</time></div><div class="pt-1"><p>Add a class attribute to the <code><body></code> element in Next.js while ensuring the attribute value is set when the page is server-rendered and correctly updated on the client when performing client-side navigation.</p></div><div class="flex justify-center mt-8 pb-5"><a class="py-1.5 px-14 mx-auto text-center text-theme-44 hover:text-black hover:bg-theme-f7 text-sm border-b border-dotted border-theme-cc tracking-widest " href="/add-class-to-body-tag-in-nextjs">Continue reading â</a></div></div><div class="pt-5 pb-10"><h1 class="text-center font-header leading-normal text-3xl mb-3"><a class="text-theme-33 hover:text-black hover:bg-theme-f7" href="/managing-multi-environment-configurations-in-nodejs">Managing Multi-Environment Configurations in Node.js with a Single Configuration File</a></h1><div class="text-center text-theme-66 text-smaller pt-2 pb-4 tracking-widest">posted <time dateTime="2013-02-02T10:00:00+00:00">on <!-- -->July 18, 2021</time></div><div class="pt-1"><p>In the past, when I was working on Node.js projects that could run under different environment settings (e.g., local, develop, staging, production), a typical pattern was to create multiple configuration files per environment. For example, a project with three environments: <code>local</code>, <code>develop</code>, and <code>production</code>, would need three configuration files. However, as the project and the team working on it grew, these configuration files often came out of sync. For example, while working on a new feature, a developer could add a new field to a <code>local</code> configuration and forget to add it to other configuration files and break other environments. Therefore, I created a small utility that improves multi-environment c
1onfiguration management by storing all configurations in a single JSON file.</p><figure class="image-container"><span style="box-sizing:border-box;display:inline-block;overflow:hidden;width:initial;height:initial;background:none;opacity:1;border:0;margin:0;padding:0;position:relative;max-width:100%"><span style="box-sizing:border-box;display:block;width:initial;height:initial;background:none;opacity:1;border:0;margin:0;padding:0;max-width:100%"><img style="display:block;max-width:100%;width:initial;height:initial;background:none;opacity:1;border:0;margin:0;padding:0" alt="" aria-hidden="true" src="data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20version=%271.1%27%20width=%27768%27%20height=%27519%27/%3e"/></span><img alt="single-config Diagram" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" decoding="async" data-nimg="intrinsic" style="position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%;filter:blur(20px);background-size:cover;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAALCAIAAAD5gJpuAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAB8klEQVQokT2Q20/TUADGj/F9f5pvmhhjgIjAQmLABTAxLl4iCYK4ONil42KWTZKxdaWW7tK1nI3Sbi2rrVmL9mxt3eiLTwbZiy/HZAv+Hr/k+5LvB3b39qgjyvd9b8Sl75uo9+f6GmO8nSDS2c/jfHA58DzP930AYWPjY1xVFcMwdP1rxzTfZ/OxPPPr9xXNVu4+mLn/cI7neeMGIEC4GU153k+EkGVZ3W73zdtVcOt2xzQlSQqtvArOL2UyB4cFmmFYudUEp2dn66nE1GxwIfS8Y5oIoW+GQRDJ4XAYj8XjsXhLaUHY4Go8V+MlSQINUVxPrS6uTNIM6bqOZVn4huhWYnpiJp3adRzHcV3P8xBCQJZbawSxsf1Sbwmo2++YFg3FC3eAMabY0sTyi+hWslSkhQpX+cLWahwwDIMuFz4Qz3L5dw2YNnR1Lbl/Z2oWykqRLS8uhz+l9hFCWruttzVVVQEnnGzu7BxXKetC1zTZtu1IJBIIBDqmSVLFEs0op6Ljuj9se2wFnJ9rmWKEqkSdXt+yvjuu+/9D7iA3P/d0IRRuyk3XdcY+gK6rx/AeLz52en17NPN3BMZYUZTp4NLkoyeHuQJb4gRe0HQdVLlqsymIIifLcr1eRwhhjMeFbCYTDr9m2fIRzeTyJEkWTyD8B8iFhQsLXoxZAAAAAElFTkSuQmCC");background-position:0% 0%"/><noscript><img alt="single-config Diagram" srcSet="https://d33wubrfki0l68.cloudfront.net/639046cf1c02873f32aa3820bb5a0fb1e4e9378e/09687/resized-images/single-config/single-config-diagram-sharp-768x519.webp 1x, https://d33wubrfki0l68.cloudfront.net/639046cf1c02873f32aa3820bb5a0fb1e4e9378e/09687/resized-images/single-config/single-config-diagram-sharp-768x519.webp 2x" src="https://d33wubrfki0l68.cloudfront.net/639046cf1c02873f32aa3820bb5a0fb1e4e9378e/09687/resized-images/single-config/single-config-diagram-sharp-768x519.webp" decoding="async" data-nimg="intrinsic" style="position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%" loading="lazy"/></noscript></span></figure></div><div class="flex justify-center mt-8 pb-5"><a class="py-1.5 px-14 mx-auto text-center text-theme-44 hover:text-black hover:bg-theme-f7 text-sm border-b border-dotted border-theme-cc tracking-widest " href="/managing-multi-environment-configurations-in-nodejs">Continue reading â</a></div></div><div class="pt-5 pb-10"><h1 class="text-center font-header leading-normal text-3xl mb-3"><a class="text-theme-33 hover:text-black hover:bg-theme-f7" href="/indexing-and-searching-arbitrary-json-data-using-elasticsearch">Indexing and Searching Arbitrary JSON Data using Elasticsearch</a></h1><div class="text-center text-theme-66 text-smaller pt-2 pb-4 tracking-widest">posted <time dateTime="2013-02-02T10:00:00+00:00">on <!-- -->October 20, 2017</time></div><div class="pt-1"><p>In this post, I would like to show you how to create an Elasticsearch index to index arbitrary JSON data, including data with nested arrays and objects. All this, without exploding the index type mapping with arbitrary properties originating from the indexed data. Nevertheless, the indexed data will still be searchable by any fields, including nested fields, by specifying their path in a "dot" notation format. In addition, I will show how to use Elasticsearch aggregations to fetch a list of all the available fields and their respective types of indexed data.</p></div><div class="flex justify-center mt-8 pb-5"><a class="py-1.5 px-14 mx-auto text-center text-theme-44 hover:text-black hover:bg-theme-f7 text-sm border-b border-dotted border-theme-cc tracking-widest " href="/indexing-and-searching-arbitrary-json-data-using-elasticsearch">Continue reading â</a></div></div><div class="pt-5 pb-10"><h1 class="text-center font-header leading-normal text-3xl mb-3"><a class="text-theme-33 hover:text-black hover:bg-theme-f7" href="/extending-events-and-properties-of-backbone-views">Extending events and properties of Backbone views</a></h1><div class="text-center text-theme-66 text-smaller pt-2 pb-4 tracking-widest">posted <time dateTime="2013-02-02T10:00:00+00:00">on <!-- -->February 7, 2015</time></div><div class="pt-1"><p>When extending Backbone views using the <a href="https://backbonejs.org/#View-extend" rel="noreferrer" target="_blank">Backbone.View.extend</a> method, the properties passed to the <code>extend</code> method will override properties defined in the super view. For example, if the super view has an <a href="https://backbonejs.org/#View-events" rel="noreferrer" target="_blank"><code>events</code></a> hash, any <code>events</code> passed to the <code>extend</code> method will override super view's events. Sometimes this might be a desirable result. However, usually when we want to "extend" something we want to add extra functionality rather redefining it existing one.</p></div><div class="flex justify-center mt-8 pb-5"><a class="py-1.5 px-14 mx-auto text-center text-theme-44 hover:text-black hover:bg-theme-f7 text-sm border-b border-dotted border-theme-cc tracking-widest " href="/extending-events-and-properties-of-backbone-views">Continue reading â</a></div></div><div class="pt-5 pb-10"><h1 class="text-center font-header leading-normal text-3xl mb-3"><a class="text-theme-33 hover:text-black hover:bg-theme-f7" href="/synchronizing-rotation-animation-between-the-keyboard-and-the-attached-view-part-2">Synchronizing rotation animation between the keyboard and the attached view - Part 2</a></h1><div class="text-center text-theme-66 text-smaller pt-2 pb-4 tracking-widest">posted <time dateTime="2013-02-02T10:00:00+00:00">on <!-- -->January 28, 2015</time></div><div class="pt-1"><p>In the [first part][first_part] of this post, I have explained how to synchronize rotation animation in iOS between the virtual keyboard and the view floating above it. Many things have been changed since then, iOS 6, iOS 7, and now iOS 8, which is why I am writing the second part of that post. In short, Apple changed something in iOS 8, causing keyboard notifications observer methods to execute their code while animations are disabled. But only when notifications are posted due to an interface orientation change while the keyboard was visible.</p></div><div class="flex justify-center mt-8 pb-5"><a class="py-1.5 px-14 mx-auto text-center text-theme-44 hover:text-black hover:bg-theme-f7 text-sm border-b border-dotted border-theme-cc tracking-widest " href="/synchronizing-rotation-animation-between-the-keyboard-and-the-attached-view-part-2">Continue reading â</a></div></div><div class="pt-5 pb-10"><h1 class="text-center font-header leading-normal text-3xl mb-3"><a class="text-theme-33 hover:text-black hover:bg-theme-f7" href="/hit-testing-in-ios">Hit-Testing in iOS</a></h1><div class="text-center text-theme-66 text-smaller pt-2 pb-4 tracking-widest">posted <time dateTime="2013-02-02T10:00:00+00:00">on <!-- -->April 22, 2014</time></div><div class="pt-1"><p>Hit-testing is the process of determining whether a point, such as a touch-point intersects with a given graphical object presented on the screen, such as <code>UIView</code>. iOS uses hit-testing to determine which <code>UIView</code> is the frontmost view under the userâs finger that should receive the touch event. It implements it by searching the view hierarchy using a reverse pre-order depth-first traversal algorithm.</p></div><div class="flex justify-center mt-8 pb-5"><a class="py-1.5 px-14 mx-auto text-center text-theme-44 hover:text-black hover:bg-theme-f7 text-sm border-b border-dotted border-theme-cc tracking-widest " href="/hit-testing-in-ios">Continue reading â</a></div></div><div class="pt-5 pb-10"><h1 class="text-center font-header leading-normal text-3xl mb-3"><a class="text-theme-33 hover:text-black hover:bg-theme-f7" href="/synchronizing-rotation-animation-between-the-keyboard-and-the-attached-view">Synchronizing rotation animation between the keyboard and the attached view</a></h1><div class="text-center text-theme-66 text-smaller pt-2 pb-4 tracking-widest">posted <time dateTime="2013-02-02T10:00:00+00:00">on <!-- -->September 21, 2013</time></div><div class="pt-1"><p>In this post, I would like to show how to make a <code>UIView</code> stay attached to the top of the iPhoneâs keyboard while the keyboard is animated. This solution works when the keyboard is animated while being presented or dismissed and when the iPhone is rotated and the interface orientation is changed.</p><iframe width="560" height="315" title="Synchronizing rotation animation between the keyboard and the attached view in iOS" src="https://www.youtube-nocookie.com/embed/Pl6OCwLfEWo" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" style="margin:auto;display:block;margin-bottom:20px;max-width:100%;:"></iframe></div><div class="flex justify-center mt-8 pb-5"><a class="py-1.5 px-14 mx-auto text-center text-theme-44 hover:text-black hover:bg-theme-f7 text-sm border-b border-dotted border-theme-cc tracking-widest " href="/synchronizing-rotation-animation-between-the-keyboard-and-the-attached-view">Continue reading â</a></div></div><div class="pt-5 pb-10"><h1 class="text-center font-header leading-normal text-3xl mb-3"><a class="text-theme-33 hover:text-black hover:bg-theme-f7" href="/web-font-loading-detection-without-timers">Web font loading detection, without timers</a></h1><div class="text-center text-theme-66 text-smaller pt-2 pb-4 tracking-widest">posted <time dateTime="2013-02-02T10:00:00+00:00">on <!-- -->March 23, 2013</time></div><div class="pt-1"><p>A small JavaScript utility function that detects and notifies you when particular fonts have been loaded and are ready to be used on a webpage without using timers.</p></div><div class="flex justify-center mt-8 pb-5"><a class="py-1.5 px-14 mx-auto text-center text-theme-44 hover:text-black hover:bg-theme-f7 text-sm border-b border-dotted border-theme-cc tracking-widest " href="/web-font-loading-detection-without-timers">Continue reading â</a></div></div><div class="pt-5 pb-10"><h1 class="text-center font-header leading-normal text-3xl mb-3"><a class="text-theme-33 hover:text-black hover:bg-theme-f7" href="/customizing-appearance-of-uisegmentedcontrol">Customizing the appearance of UISegmentedControl</a></h1><div class="text-center text-theme-66 text-smaller pt-2 pb-4 tracking-widest">posted <time dateTime="2013-02-02T10:00:00+00:00">on <!-- -->January 20, 2013</time></div><div class="pt-1"><p>The <a href="https://developer.apple.com/library/ios/#documentation/uikit/reference/UISegmentedControl_Class/Reference/UISegmentedControl.html" rel="noreferrer" target="_blank">UISegmentedControl Class</a> is a UI control available in the <a href="https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIKit_Framework/_index.html" rel="noreferrer" target="_blank">UIKit framework</a>.</p><figure class="image-container"><span style="box-sizing:border-box;display:inline-block;overflow:hidden;width:initial;height:initial;background:none;opacity:1;border:0;margin:0;padding:0;position:relative;max-width:100%"><span style="box-sizing:border-box;display:block;width:initial;height:initial;background:none;opacity:1;border:0;margin:0;padding:0;max-width:100%"><img style="display:block;max-width:100%;width:initial;height:initial;background:none;opacity:1;border:0;margin:0;padding:0" alt="" aria-hidden="true" src="data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20version=%271.1%27%20width=%27251%27%20height=%2749%27/%3e"/></span><img alt="Generic segmented control" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" decoding="async" data-nimg="intrinsic" style="position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%;filter:blur(20px);background-size:cover;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAADCAIAAAAV0xgDAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAnklEQVQImQGTAGz/APP2+666yLS/zbC9ybzEz8TL1MTL1MPJ08TL1MXL1cTL1MPJ08XL1cTL1MDI0f3//wCImrAACzkfR2oABDUTPV9nepNheJFGX3o6V3NqgJZXb4gtTWk8V3VqfZQ1U3DCy9YA4Ofueo6mhZWtfpGpoK+/tsPRs7/Osb3LsL3Js7/Os7/OrrrIsb3LtsPQrbrJ9vr+wxNcPK2zEzIAAAAASUVORK5CYII=");background-position:0% 0%"/><noscript><img alt="Generic segmented control" srcSet="https://d33wubrfki0l68.cloudfront.net/8989c4d827be530e68f34a61135415dd872390f4/c6aa7/resized-images/segmented-control/generic-segmented-control-sharp-251x49.webp 1x, https://d33wubrfki0l68.cloudfront.net/8989c4d827be530e68f34a61135415dd872390f4/c6aa7/resized-images/segmented-control/generic-segmented-control-sharp-251x49.webp 2x" src="https://d33wubrfki0l68.cloudfront.net/8989c4d827be530e68f34a61135415dd872390f4/c6aa7/resized-images/segmented-control/generic-segmented-control-sharp-251x49.webp" decoding="async" data-nimg="intrinsic" style="position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%" loading="lazy"/></noscript></span></figure><p>The appearance of this control can be easily customized. However, customizing this control is not always apparent to new iOS developers. I was recently asked to create a row of separate buttons that would behave similarly to <code>UISegmentedControl</code>. That is, only one button could be selected at any given time. The design looked something like this (specifically this one I took from <a href="https://dribbble.com/shots/588694-Blue-Milk-UI-Kit-Updated" rel="noreferrer" target="_blank">dribbble</a>)</p><figure class="image-container"><span style="box-sizing:border-box;display:inline-block;overflow:hidden;width:initial;height:initial;background:none;opacity:1;border:0;margin:0;padding:0;position:relative;max-width:100%"><span style="box-sizing:border-box;display:block;width:initial;height:initial;background:none;opacity:1;border:0;margin:0;padding:0;max-width:100%"><img style="display:block;max-width:100%;width:initial;height:initial;background:none;opacity:1;border:0;margin:0;padding:0" alt="" aria-hidden="true" src="data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20version=%271.1%27%20width=%27294%27%20height=%2748%27/%3e"/></span><img alt="Custom segmented control" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" decoding="async" data-nimg="intrinsic" style="position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%;filter:blur(20px);background-size:cover;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAACCAIAAADej8umAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAbUlEQVQImQFiAJ3/AGB4gAAOHwAOH1x3fv/++tTS0c7Myu3q6Ofn5uTf383IyO/r6u3q6d3a1+Pf3d7d3QBggYcAUVsAUVthgojg2NeXlZWTkI7MyMfg3t65trOTkI7S0c/i3t6npaOzsK3d3dz6IEEspB2XqgAAAABJRU5ErkJggg==");background-position:0% 0%"/><noscript><img alt="Custom segmented control" srcSet="https://d33wubrfki0l68.cloudfront.net/6f0345419ce180b0f8eaa1bbe94988b2a0fd6556/0d883/resized-images/segmented-control/custom-segmented-control-sharp-294x48.webp 1x, https://d33wubrfki0l68.cloudfront.net/6f0345419ce180b0f8eaa1bbe94988b2a0fd6556/0d883/resized-images/segmented-control/custom-segmented-control-sharp-294x48.webp 2x" src="https://d33wubrfki0l68.cloudfront.net/6f0345419ce
1180b0f8eaa1bbe94988b2a0fd6556/0d883/resized-images/segmented-control/custom-segmented-control-sharp-294x48.webp" decoding="async" data-nimg="intrinsic" style="position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%" loading="lazy"/></noscript></span></figure></div><div class="flex justify-center mt-8 pb-5"><a class="py-1.5 px-14 mx-auto text-center text-theme-44 hover:text-black hover:bg-theme-f7 text-sm border-b border-dotted border-theme-cc tracking-widest " href="/customizing-appearance-of-uisegmentedcontrol">Continue reading â</a></div></div><div class="pt-5 pb-10"><h1 class="text-center font-header leading-normal text-3xl mb-3"><a class="text-theme-33 hover:text-black hover:bg-theme-f7" href="/javascript-profiler">JavaScript Profiler</a></h1><div class="text-center text-theme-66 text-smaller pt-2 pb-4 tracking-widest">posted <time dateTime="2013-02-02T10:00:00+00:00">on <!-- -->August 6, 2012</time></div><div class="pt-1"><p>A custom method for profiling JavaScript execution times and getting the results as an object, showing them on the screen, or printing to console.</p><figure class="image-container"><span style="box-sizing:border-box;display:inline-block;overflow:hidden;width:initial;height:initial;background:none;opacity:1;border:0;margin:0;padding:0;position:relative;max-width:100%"><span style="box-sizing:border-box;display:block;width:initial;height:initial;background:none;opacity:1;border:0;margin:0;padding:0;max-width:100%"><img style="display:block;max-width:100%;width:initial;height:initial;background:none;opacity:1;border:0;margin:0;padding:0" alt="" aria-hidden="true" src="data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20version=%271.1%27%20width=%27642%27%20height=%27261%27/%3e"/></span><img alt="JavaScript Profiler HTML Result Table" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" decoding="async" data-nimg="intrinsic" style="position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%;filter:blur(20px);background-size:cover;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAGCAIAAABFHomwAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA10lEQVQYlTXOIY6GMBCG4d4Cyy04AYdA4BD1CJIafAUCSfLLiooVCOQIREXFCMQYBAKBQCAqaiq62fLvYyfvl2GMMc75MAzjOE7TtCyLlFII0SVVVXVd1yec8zzPWVEURAQAx3F472OMT4KIRHTfdwjhSGKMQghW1zURaa2ttWey7zsirutqjHHOvTEieu/btv0LrutSSgGAMeYNrLUmeZ7HOWeTEMI3iDFaawFg3/cY43meRLRt27v6XhHx+1KWZX3fN00jpdRaz/OslPr8+0mmBADKsvwFx/fJZ24XIlkAAAAASUVORK5CYII=");background-position:0% 0%"/><noscript><img alt="JavaScript Profiler HTML Result Table" srcSet="https://d33wubrfki0l68.cloudfront.net/166cbb5bf4f9c14918d565ab7441d50de732982b/37357/resized-images/js-profiler/javascript-profiler-html-table-sharp-642x261.webp 1x, https://d33wubrfki0l68.cloudfront.net/166cbb5bf4f9c14918d565ab7441d50de732982b/37357/resized-images/js-profiler/javascript-profiler-html-table-sharp-642x261.webp 2x" src="https://d33wubrfki0l68.cloudfront.net/166cbb5bf4f9c14918d565ab7441d50de732982b/37357/resized-images/js-profiler/javascript-profiler-html-table-sharp-642x261.webp" decoding="async" data-nimg="intrinsic" style="position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%" loading="lazy"/></noscript></span></figure></div><div class="flex justify-center mt-8 pb-5"><a class="py-1.5 px-14 mx-auto text-center text-theme-44 hover:text-black hover:bg-theme-f7 text-sm border-b border-dotted border-theme-cc tracking-widest " href="/javascript-profiler">Continue reading â</a></div></div><div class="pt-5 pb-10"><h1 class="text-center font-header leading-normal text-3xl mb-3"><a class="text-theme-33 hover:text-black hover:bg-theme-f7" href="/waiting-for-web-fonts-to-load">Waiting for web fonts to load</a></h1><div class="text-center text-theme-66 text-smaller pt-2 pb-4 tracking-widest">posted <time dateTime="2013-02-02T10:00:00+00:00">on <!-- -->August 2, 2012</time></div><div class="pt-1"><p>A small JavaScript utility function that detects and notifies you when particular fonts have been loaded and are ready to be used on a webpage</p></div><div class="flex justify-center mt-8 pb-5"><a class="py-1.5 px-14 mx-auto text-center text-theme-44 hover:text-black hover:bg-theme-f7 text-sm border-b border-dotted border-theme-cc tracking-widest " href="/waiting-for-web-fonts-to-load">Continue reading â</a></div></div></div></div></main><footer class="pb-6"><div class="wide-container border-t border-theme-ee pt-3.5 px-4 text-base"><ul class="flex flex-row justify-center items-center text-2xl h-8"><li><a class="text-theme-33 px-2 py-2" href="https://linkedin.com/in/smnh" aria-label="LinkedIn"><svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="linkedin" class="svg-inline--fa fa-linkedin " role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
1<path fill="currentColor" d="M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3zM135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5 0 21.3-17.2 38.5-38.5 38.5zm282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9V416z"></path></svg></a></li><li><a class="text-theme-33 px-2 py-2" href="https://github.com/smnh" aria-label="GitHub"><svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="github-square" class="svg-inline--fa fa-github-square " role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zM277.3 415.7c-8.4 1.5-11.5-3.7-11.5-8 0-5.4 .2-33 .2-55.3 0-15.6-5.2-25.5-11.3-30.7 37-4.1 76-9.2 76-73.1 0-18.2-6.5-27.3-17.1-39 1.7-4.3 7.4-22-1.7-45-13.9-4.3-45.7 17.9-45.7 17.9-13.2-3.7-27.5-5.6-41.6-5.6-14.1 0-28.4 1.9-41.6 5.6 0 0-31.8-22.2-45.7-17.9-9.1 22.9-3.5 40.6-1.7 45-10.6 11.7-15.6 20.8-15.6 39 0 63.6 37.3 69 74.3 73.1-4.8 4.3-9.1 11.7-10.6 22.3-9.5 4.3-33.8 11.7-48.3-13.9-9.1-15.8-25.5-17.1-25.5-17.1-16.2-.2-1.1 10.2-1.1 10.2 10.8 5 18.4 24.2 18.4 24.2 9.7 29.7 56.1 19.7 56.1 19.7 0 13.9 .2 36.5 .2 40.6 0 4.3-3 9.5-11.5 8-66-22.1-112.2-84.9-112.2-158.3 0-91.8 70.2-161.5 162-161.5S388 165.6 388 257.4c.1 73.4-44.7 136.3-110.7 158.3zm-98.1-61.1c-1.9 .4-3.7-.4-3.9-1.7-.2-1.5 1.1-2.8 3-3.2 1.9-.2 3.7 .6 3.9 1.9 .3 1.3-1 2.6-3 3zm-9.5-.9c0 1.3-1.5 2.4-3.5 2.4-2.2 .2-3.7-.9-3.7-2.4 0-1.3 1.5-2.4 3.5-2.4 1.9-.2 3.7 .9 3.7 2.4zm-13.7-1.1c-.4 1.3-2.4 1.9-4.1 1.3-1.9-.4-3.2-1.9-2.8-3.2 .4-1.3 2.4-1.9 4.1-1.5 2 .6 3.3 2.1 2.8 3.4zm-12.3-5.4c-.9 1.1-2.8 .9-4.3-.6-1.5-1.3-1.9-3.2-.9-4.1 .9-1.1 2.8-.9 4.3 .6 1.3 1.3 1.8 3.3 .9 4.1zm-9.1-9.1c-.9 .6-2.6 0-3.7-1.5s-1.1-3.2 0-3.9c1.1-.9 2.8-.2 3.7 1.3 1.1 1.5 1.1 3.3 0 4.1zm-6.5-9.7c-.9 .9-2.4 .4-3.5-.6-1.1-1.3-1.3-2.8-.4-3.5 .9-.9 2.4-.4 3.5 .6 1.1 1.3 1.3 2.8 .4 3.5zm-6.7-7.4c-.4 .9-1.7 1.1-2.8 .4-1.3-.6-1.9-1.7-1.5-2.6 .4-.6 1.5-.9 2.8-.4 1.3 .7 1.9 1.8 1.5 2.6z"></path></svg></a></li></ul><div class="px-2.5 text-theme-66 text-center"><p>2021 © smnh.</p></div></div></footer></div></div>
1<script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"site":{"__metadata":{"id":"content/data/site.json","source":"sourcebit-source-filesystem","sourceName":"data","sourcePath":"/opt/build/repo/content/data","relSourcePath":"site.json","relProjectPath":"content/data/site.json"},"title":"smnh","domain":"https://smnh.me"},"posts":[{"__metadata":{"id":"content/pages/posts/2022-12-21-extending-typescript-interfaces-and-type-aliases-with-common-properties.md","source":"sourcebit-source-filesystem","sourceName":"pages","sourcePath":"/opt/build/repo/content/pages","relSourcePath":"posts/2022-12-21-extending-typescript-interfaces-and-type-aliases-with-common-properties.md","relProjectPath":"content/pages/posts/2022-12-21-extending-typescript-interfaces-and-type-aliases-with-common-properties.md"},"layout":"post","title":"Extending TypeScript Interfaces and Type Aliases with common properties","slug":"extending-typescript-interfaces-and-type-aliases-with-common-properties","date":"2022-12-21","tags":["typescript","interfaces","type-aliases","types","extend","extension","override","union","intersection","common","optional","required"],"description":"How TypeScript extends Interfaces and intersects Type Aliases that have common properties of different types.","excerpt":"In this article, I want to discuss what happens when extending TypeScript Interfaces and intersecting Type Aliases that have common properties of different types.\n\n\u003cImage src=\"/resized-images/extending-typescript-interfaces-and-type-aliases-with-common-properties/typescript-type-aliases-intersection-diagram-sharp-768x576.webp\" alt=\"TypeScript type intersection diagram\" base64=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAMCAIAAADkharWAAAACXBIWXMAABcSAAAXEgFnn9JSAAABoUlEQVQokW2QsUsbURzH7w9w0yEUgou4Bdql4OKiUUyFdgpBHIQDSYZ0SOka5YkECifkLkM6vBZ6gxKIiMng05o3XYY3vQvmRcyL6CHcIb6lb3pLr+SuQ0nzmX8/Pt/vV/O8p0azhdDPu+GQscHj44NSKoxQSp21Lpun567r3vSZ67pCCI2xwezs3Kvk4vrm9uu3G+DgS8+lJxGc89X0mqbNLK9ks1u75b3DER9q96PRkWHUv35vNNsXCF1dXUv5izGGMRZCmGa1XN6v1b5ddzoYY865JoRot9v1et22f0gplVJCCKWUlDKchhaGoW3buq7ncjkAgGEY+XweQug4DsaYEOI4XcfpUkoJIVLKsQFCaFlWfIEQ8jyPEEIpDXxfRcQDxP6xwTCMYrGoRwAAEEJxhyinjTG2IzDGfyNRSiuVSiaTAQBACE2zallWqfQplUql02mEUOD7nucFvj9+iKWxMSYInhkb9Ps9IV7iGJOlJyh+/JycX3j3Yef4pBGGv6esNEGhkE8kEm+W3lfN2vRZ/0UpRSlljN0Objnn/z/8AT/jwKl9VN6hAAAAAElFTkSuQmCC\" width=\"768\" height=\"576\" hasLargerVersion=\"true\" /\u003e\n","markdown_content":"\nIn this article, I want to discuss what happens when extending TypeScript Interfaces and intersecting Type Aliases that have common properties of different types.\n\n## Extending Interfaces\n\nLet's start with interfaces first. Assume an interface `IBase` that has an optional property `prop_b`:\n\n```ts\ninterface IBase {\n prop_a: string;\n prop_b?: string;\n proc_c: string;\n}\n```\n\nLet's say we want to extend this interface with another interface that adds a new property `prop_new` but also changes the property `prop_b` to a required property and `prop_a` to an optional property:\n\n```ts\ninterface IExtendBase extends IBase {\n prop_a?: string;\n prop_b: string;\n prop_new: string;\n}\n```\n\nIf you [try that](https://www.typescriptlang.org/play?target=2#code/JYOwLgpgTgZghgYwgAgJICE4GcUG8BQyyADlAPbED6cAXMlmFKAOYDchJ5VARgPx0MmINh1JkElBAMYt2AX3z5QkWIhSoAogA9IIACaYcyCDoj6saQ3g5ExVOP3ozh7Ira6Vu0oSLecKlCAQAO7esvhyQA), you will see the following TypeScript error:\n\n```text\nInterface 'IExtendBase' incorrectly extends interface 'IBase'.\n Types of property 'prop_a' are incompatible.\n Type 'string | undefined' is not assignable to type 'string'.\n Type 'undefined' is not assignable to type 'string'.\n```\n\n\u003cImage\n src=\"/images/extending-typescript-interfaces-and-type-aliases-with-common-properties/typescript-extend-interface-error.png\"\n title=\"Extending an interface with an optional property that overrides a required property throws an error.\"\n newWindow=\"true\"\n/\u003e\n\nAs the error suggests, this happens because the property `prop_a` in the `IExtendBase` interface cannot override the same property in the extended interface `IBase`. The `prop_a` in the `IExtendBase` has a broader set of possible value types than the same property in the `IBase` interface. By the way, this would also happen if the `prop_a` property in the `IExtendBase` wouldn't be optional but had a broader set of value types. For example, having a union of `string` and `number` would also throw an error:\n\n```ts\ninterface IExtendBase extends IBase {\n prop_a: string | number;\n prop_b: string;\n prop_new: string;\n}\n```\n\n```text\nInterface 'IExtendBase' incorrectly extends interface 'IBase'.\n Types of property 'prop_a' are incompatible.\n Type 'string | number' is not assignable to type 'string'.\n Type 'number' is not assignable to type 'string'.\n```\n\nTo fix this problem, we need to omit the `prop_a` property from the `IBase` interface before extending it:\n\n```ts\ninterface IExtendBase extends Omit\u
1003cIBase, 'prop_a'\u003e {\n prop_a?: string;\n prop_b: string;\n prop_new: string;\n}\n```\n\nNow the new extended interface will have the properties the way we want them:\n\n- The overridden `prop_a` is optional\n- The overridden `prop_b` is required\n- The original `prop_c` is not changed\n- The new required `prop_new` was added\n\n\u003cImage\n src=\"/images/extending-typescript-interfaces-and-type-aliases-with-common-properties/typescript-extend-interface-with-omit.png\"\n title=\"Extending an interface with an optional property while omitting the original required property.\"\n newWindow=\"true\"\n/\u003e\n\n[Playground Link](https://www.typescriptlang.org/play?target=2#code/JYOwLgpgTgZghgYwgAgJICE4GcUG8BQyyADlAPbED6cAXMlmFKAOYDchJ5VARgPx0MmINh1JkElBAMYt2AX3z5QkWIhSoAogA9IIACaYcyCDoj6syAPIBbYGAA8GbBAA0yAORiqcdwD5kBERe1Pz0MsLsQVyU3NJCIlEUlCAQAO5xsvgK+GAAnsQoAMrA1sQANsAwufYAKv4AvAEcANoA0sigyADWELlkMMg1ALp0NW1D8op5BfQl5ZXAEHrIjcWlFVWO2roGzr6sQA)\n\n\n## Extending Type Aliases\n\nType aliases behave similarly to interfaces. However, if we try to achieve similar behavior with types, we won't get an error message as we did before.\n\n```ts\ntype TBase = {\n prop_a: string;\n prop_b?: string;\n prop_c: string;\n}\n\ntype TExtendBase = TBase \u0026 {\n prop_a?: string;\n prop_b: string;\n prop_new: string;\n};\n```\n\nInstead, the `prop_a` in the `TExtendBase` type will **be required, not optional**!\n\n\u003cImage\n src=\"/images/extending-typescript-interfaces-and-type-aliases-with-common-properties/typescript-extend-type-alias-required.png\"\n title=\"Extending type alias with optional property leaves it required.\"\n newWindow=\"true\"\n/\u003e\n\n[Playground Link](https://www.typescriptlang.org/play?target=2#code/C4TwDgpgBAKgQgQwM7QLxQN4CgpTAJwHswB9BALiiWHwEsA7AcwG4c8jSAjAfkurqatcBYiQDGfGgxZYAvliyhIsAKIAPYBHoATRCijp4yaADJMbYRzK8qUwRfajOkgTNyXR9CAHcX01rKsiuDQAMq0ALZgADa0AGYgADwwAHwG5rgA2gDSUAxQANYQIIRxsAC6lDA55QEKStBIkTHxtBDa6eFRsQnJ6po6ehApzEA)\n\nThis happens because the intersection (`\u0026`) of two types creates a new type with all the properties included in the intersected types (union of property names). In contrast, the type of every property is an intersection of its value types in the intersected types (intersection of property types). Moreover, if the `prop_a` property in the `TExtendBase` type would have a broader set of value types, they would be intersected with the `string` type from the `TBase` type leaving a `string` type:\n\n\u003cImage\n src=\"/images/extending-typescript-interfaces-and-type-aliases-with-common-properties/typescript-extend-type-alias-string.png\"\n title=\"Extending type alias with optional property leaves it as string\"\n newWindow=\"true\"\n/\u003e\n\nHere is a diagram showing how the type alias intersection works:\n\n\u003cImage\n src=\"/images/extending-typescript-interfaces-and-type-aliases-with-common-properties/typescript-type-aliases-intersection-diagram.png\"\n title=\"TypeScript type alias intersection diagram\"\n newWindow=\"true\"\n/\u003e\n\nInterestingly, a naive intersection logic can be achieved using the following type:\n\n```ts\ntype TypeA = {\n prop_a: string | boolean;\n prop_b: string | number;\n prop_c?: string;\n prop_d: string;\n}\n\ntype TypeB = {\n prop_a: boolean | number;\n prop_b: number | boolean;\n prop_c: string;\n prop_e: string;\n}\n\ntype IntersectTypes\u003cType1, Type2\u003e = {\n [Property in (keyof Type1 | keyof Type2)]:\n Property extends (keyof Type1 \u0026 keyof Type2)\n ? Type1[Property] \u0026 Type2[Property]\n : Property extends Exclude\u003ckeyof Type1, keyof Type2\u003e\n ? Type1[Property]\n : Property extends Exclude\u003ckeyof Type2, keyof Type1\u003e\n ? Type2[Property]\n : never;\n}\n\ntype TypeC = IntersectTypes\u003cTypeA, TypeB\u003e;\n```\n\n\u003cImage\n src=\"/images/extending-typescript-interfaces-and-type-aliases-with-common-properties/typescript-custom-intersection-type.png\"\n title=\"TypeScript custom intersection type\"\n newWindow=\"true\"\n/\u003e\n\n### The fix\n\nJust like with interfaces, to fix this problem, we need to omit the `prop_a` property from the `TBase` type before intersecting it:\n\n```ts\ntype TBase = {\n prop_a: string;\n prop_b?: string;\n prop_c: string;\n}\n\ntype TExtendBase = Omit\u003cTBase, 'prop_a'\u003e \u0026 {\n prop_a?: string;\n prop_b: string;\n prop_new: string;\n};\n```\n\n\u003cImage\n src=\"/images/extending-typescript-interfaces-and-type-aliases-with-common-properties/typescript-extend-type-with-omit.png\"\n title=\"Extending type alias with an optional property while omitting the extended property\"\n newWindow=\"true\"\n/\u003e\n\n[Playground Link](https://www.t
1ypescriptlang.org/play?target=2#code/C4TwDgpgBAKgQgQwM7QLxQN4CgpTAJwHswB9BALiiWHwEsA7AcwG4c8jSAjAfkurqatcBYiQDGfGgxZYAvliyhIsAKIAPYBHoATRCijoA8gFtawADzxkEADRQA5CNIJ7APigAyTG2EcyvKilBH3ZRTkkBGVxfUXoIAHcI6VZZVkVwaABlWmMwABtaADMQS3d0bFwAbQBpKAYoAGsIEEJC2ABdShga9pSFJWgkHPyi2ghtAyhs3ILiy3VNHT0IV2YgA)\n","urlPath":"/extending-typescript-interfaces-and-type-aliases-with-common-properties"},{"__metadata":{"id":"content/pages/posts/2021-07-31-add-class-to-body-tag-in-nextjs.md","source":"sourcebit-source-filesystem","sourceName":"pages","sourcePath":"/opt/build/repo/content/pages","relSourcePath":"posts/2021-07-31-add-class-to-body-tag-in-nextjs.md","relProjectPath":"content/pages/posts/2021-07-31-add-class-to-body-tag-in-nextjs.md"},"layout":"post","title":"How to add a class to the body tag in Next.js","slug":"add-class-to-body-tag-in-nextjs","date":"2021-07-31","tags":["next.js","body","class","className","attribute","_document.js"],"description":"Add a class attribute to the body element in Next.js using _document.js while ensuring the attribute value is set when the page is server-rendered and correctly updated on the client when performing client-side navigation.","excerpt":"Add a class attribute to the `\u003cbody\u003e` element in Next.js while ensuring the attribute value is set when the page is server-rendered and correctly updated on the client when performing client-side navigation.\n","markdown_content":"\nIf you are familiar with [React Helmet](https://www.npmjs.com/package/react-helmet), then you probably know that you can set attributes, such as `className`, on the `\u003cbody\u003e` tag from any component in your code. Of course, the value of the `className` does not have to be static. You can use component's `props` to set the value dynamically:\n\n```jsx\nimport React from 'react';\nimport { Helmet } from 'react-helmet';\n\nexport default function Layout(props) {\n return (\n \u003cdiv\u003e\n \u003cHelmet\u003e\n \u003cmeta charSet=\"utf-8\" /\u003e\n \u003ctitle\u003e{props.title}\u003c/title\u003e\n \u003cbody className={props.isDark ? 'dark-mode' : 'light-mode'} /\u003e\n \u003c/Helmet\u003e\n \u003c/div\u003e\n );\n}\n```\n\nUnfortunately, you can't set body class with Next's [`next/head`](https://nextjs.org/docs/api-reference/next/head), at least not officially.\n\nIf you try to do this anyway like this:\n\n```jsx\n// src/pages/index.js\n\nimport Head from 'next/head';\n\nexport async function getStaticProps() {\n return { props: { isDark: true } };\n}\n\nexport default function IndexPage(props) {\n return (\n \u003cdiv\u003e\n \u003cHead\u003e\n \u003ctitle\u003eMy page title\u003c/title\u003e\n \u003cbody className={props.isDark ? 'dark-mode' : 'light-mode'} /\u003e\n \u003c/Head\u003e\n \u003cp\u003eHello world!\u003c/p\u003e\n \u003c/div\u003e\n );\n}\n```\n\nNext.js will mistakenly pull tags from `\u003chead\u003e` and put them into `\u003cbody\u003e`, and it will throw the `Warning: next-head-count is missing.` error. Ironically, you will get the class inside the `\u003cbody\u003e`:\n\n\u003cImage\n src=\"/images/nextjs-body-class/nextjs-body-class-bug.png\"\n alt=\"next.js body class bug\"\n newWindow=\"true\"\n/\u003e\n\nSo what should you do?\n\n## Step 1\n\nLuckily, Next.js provides a way to customize your `\u003chtml\u003e` and `\u003cbody\u003e` tags. All you need to do is to use `this.props.__NEXT_DATA__.props.pageProps` to access the page props:\n\n```jsx\n// src/pages/_document.js\n\nimport Document, { Html, Head, Main, NextScript } from 'next/document';\n\nexport default class MyDocument extends Document {\n render() {\n const pageProps = this.props?.__NEXT_DATA__?.props?.pageProps;\n return (\n \u003cHtml\u003e\n \u003cHead /\u003e\n \u003cbody className={pageProps.isDark ? 'dark-mode' : 'light-mode'}\u003e\n \u003cMain /\u003e\n \u003cNextScript /\u003e\n \u003c/body\u003e\n \u003c/Html\u003e\n );\n }\n}\n```\n\nNow, if you remove the `\u003cbody\u003e` tag from `\u003cHead\u003e`, the class will be there, and no errors will be thrown:\n\n```jsx\n// src/pages/index.js\n\nexport default function IndexPage(props) {\n return (\n \u003cdiv\u003e\n \u003cHead\u003e\n \u003ctitle\u003eMy page title\u003c/title\u003e\n \u003c/Head\u003e\n \u003cp\u003eHello world!\u003c/p\u003e\n \u003c/div\u003e\n );\n}\n```\n\n\u003cImage\n src=\"/images/nextjs-body-class/nextjs-body-class-fix.png\"\n alt=\"next.js body class fix\"\n newWindow=\"true\"\n/\u003e\n\nNow, you have your body class set dynamically from page props. But that's not the end of the story :)\n\n## Step 2\n\nThe problem is that the `_document.js` is only rendered on the server. When the browser loads the first page, it will get the correct class. But, if your need to have a different class per page, then while navigating between these pages, the class will not be updated.\n\nLet's add another page `src/pages/about.js` with `isDark: false` and a link between About and Home pages:\n\n```jsx\n// src/pages/index.js\n\nimport Link from 'next/link';\nimport Head from 'next/head';
1\n\nexport async function getStaticProps() {\n return { props: { isDark: true } };\n}\n\nexport default function IndexPage(props) {\n return (\n \u003cdiv\u003e\n \u003cHead\u003e\n \u003ctitle\u003eHome Page\u003c/title\u003e\n \u003c/Head\u003e\n \u003ch1\u003eHome Page\u003c/h1\u003e\n \u003cdiv\u003e\n \u003cLink href=\"/about\"\u003eAbout page\u003c/Link\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n );\n}\n```\n\n```jsx\n// src/pages/about.js\n\nimport Link from 'next/link';\nimport Head from 'next/head';\n\nexport async function getStaticProps() {\n return { props: { isDark: false } };\n}\n\nexport default function AboutPage() {\n return (\n \u003cdiv\u003e\n \u003cHead\u003e\n \u003ctitle\u003eAbout page\u003c/title\u003e\n \u003c/Head\u003e\n \u003ch1\u003eAbout page\u003c/h1\u003e\n \u003cdiv\u003e\n \u003cLink href=\"/\"\u003eHome page\u003c/Link\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n );\n}\n```\n\n1. Navigate to the home page `/`. You will see the body has the correct `dark-mode` class, good.\n2. Click on the \"About page\" link and check the body's class. It still has the `dark-mode` value! This is because `_document.js` is only rendered on the server, and the client-side navigation via `next/link` happens on the client-side.\n3. Refresh the browser - you will see that now the body has the correct `light-mode` class. This is because when you've refreshed the page, you have requested the server to render the \"about\" page. And because it was generated on the server, it has the correct class.\n\nTo fix that, we can update the `_app.js` with a simple `useEffect()` hook that updates the body class:\n\n```jsx\n// src/page/_app.js\n\nimport React, { useEffect } from 'react';\n\nexport default function MyApp({ Component, pageProps }) {\n useEffect(() =\u003e {\n document.body.className = pageProps.isDark ? 'dark-mode' : 'light-mode';\n });\n return \u003cComponent {...pageProps} /\u003e;\n}\n```\n\nNow, try to go over the same steps again. You will see that the body class updates between page navigations. Yay ð!\n\nYou might ask yourself why wouldn't you always use the `useEffect` hook and throw away the changes in `_document.js`? The changes in `_document.js` ensure that when the browser loads your page, the HTML will include the body class, and your site's visitor will not need to wait for React hydration to run the `useEffect` hook and apply the class.\n","urlPath":"/add-class-to-body-tag-in-nextjs"},{"__metadata":{"id":"content/pages/posts/2021-07-18-managing-multi-environment-configurations-with-single-config.md","source":"sourcebit-source-filesystem","sourceName":"pages","sourcePath":"/opt/build/repo/content/pages","relSourcePath":"posts/2021-07-18-managing-multi-environment-configurations-with-single-config.md","relProjectPath":"content/pages/posts/2021-07-18-managing-multi-environment-configurations-with-single-config.md"},"layout":"post","title":"Managing Multi-Environment Configurations in Node.js with a Single Configuration File","slug":"managing-multi-environment-configurations-in-nodejs","date":"2021-07-18","tags":["node","configuration","config","environments","npm","node","single-config"],"description":"A JavaScript utility that helps manage multi-environment configurations in Node.js with a single JSON configuration file.","excerpt":"In the past, when I was working on Node.js projects that could run under different environment settings (e.g., local, develop, staging, production), a typical pattern was to create multiple configuration files per environment. For example, a project with three environments: `local`, `develop`, and `production`, would need three configuration files. However, as the project and the team working on it grew, these configuration files often came out of sync. For example, while working on a new feature, a developer could add a new field to a `local` configuration and forget to add it to other configuration files and break other environments. Therefore, I created a small utility that improves multi-environment c
1onfiguration management by storing all configurations in a single JSON file.\n\n\u003cImage src=\"/resized-images/single-config/single-config-diagram-sharp-768x519.webp\" alt=\"single-config Diagram\" base64=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAALCAIAAAD5gJpuAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAB8klEQVQokT2Q20/TUADGj/F9f5pvmhhjgIjAQmLABTAxLl4iCYK4ONil42KWTZKxdaWW7tK1nI3Sbi2rrVmL9mxt3eiLTwbZiy/HZAv+Hr/k+5LvB3b39qgjyvd9b8Sl75uo9+f6GmO8nSDS2c/jfHA58DzP930AYWPjY1xVFcMwdP1rxzTfZ/OxPPPr9xXNVu4+mLn/cI7neeMGIEC4GU153k+EkGVZ3W73zdtVcOt2xzQlSQqtvArOL2UyB4cFmmFYudUEp2dn66nE1GxwIfS8Y5oIoW+GQRDJ4XAYj8XjsXhLaUHY4Go8V+MlSQINUVxPrS6uTNIM6bqOZVn4huhWYnpiJp3adRzHcV3P8xBCQJZbawSxsf1Sbwmo2++YFg3FC3eAMabY0sTyi+hWslSkhQpX+cLWahwwDIMuFz4Qz3L5dw2YNnR1Lbl/Z2oWykqRLS8uhz+l9hFCWruttzVVVQEnnGzu7BxXKetC1zTZtu1IJBIIBDqmSVLFEs0op6Ljuj9se2wFnJ9rmWKEqkSdXt+yvjuu+/9D7iA3P/d0IRRuyk3XdcY+gK6rx/AeLz52en17NPN3BMZYUZTp4NLkoyeHuQJb4gRe0HQdVLlqsymIIifLcr1eRwhhjMeFbCYTDr9m2fIRzeTyJEkWTyD8B8iFhQsLXoxZAAAAAElFTkSuQmCC\" width=\"768\" height=\"519\" hasLargerVersion=\"true\" /\u003e\n","markdown_content":"\nIn the past, when I was working on Node.js projects that could run under different environment settings (e.g., local, develop, staging, production), a typical pattern was to create multiple configuration files per environment. For example, a project with three environments: `local`, `develop,` and `production`, would need three configuration files:\n\n```treeview\nproject_root\nâââ config.local.json\nâââ config.dev.json\nâââ config.prod.json\n```\n\nEach of these files defines an object with a similar structure but different configuration values for every environment. For example, the `config.local.json` might define the hosts for the local API server and the local database connection string:\n\n```json\n{\n \"apiHost\": \"https://localhost:8080\",\n \"mongoUri\": \"mongodb://localhost:27020/app\",\n \"logLevel\": \"debug\"\n}\n```\n\nThen, a `config.js` module could use a simple logic that loads the correct configuration file for the target environment, for example, by checking an environment variable such as `NODE_ENV`: \n\n```js\n// config.js\n\nimport fs from 'fs';\n\nfunction getConfigFileForEnvironment(env) {\n switch (env) {\n case 'local':\n return 'config.local.json';\n case 'development':\n return 'config.dev.json';\n case 'production':\n return 'config.prod.json';\n }\n}\n\nconst configFilePath = getConfigFileForEnvironment(process.env.NODE_ENV);\nconst rawData = fs.readFileSync(configFilePath);\nconst config = JSON.parse(rawData);\n\nexport default config;\n```\n\nHowever, as the project and the team working on it grew, these configuration files often came out of sync. For example, while working on a new feature, a developer could add a new field to a `local` configuration and forget to add it to other configuration files and break other environments. \n\nI've identified the following drawbacks when using this pattern:\n\n- No validation for missing keys between environments.\n- Risk of having out-of-sync configuration files.\n- No way to define default or fallback values.\n- Difficulty seeing all the configuration values per environment per key in one place. \n- Difficulty creating new environments\n\nTherefore, I created a small utility that improves multi-environment configuration management by storing all configurations in a single JSON file to solve these problems.\n\n## Solution - single-config\n\n[Single-Config](https://www.npmjs.com/package/single-config) is a small npm package that solves the problems described above. It lets you define a single configuration file for all environments. It can also generate types for the configuration files using TypeScript.\n\nContinuing the previous example, using single-config, you can define a single configuration file `config.json`:\n\n```json\n{\n \"_envs\": [\"local\", \"dev\", \"prod\"],\n \"apiHost\": {\n \"local\": \"https://localhost:8080\",\n \"dev\": \"https://dev.api.example.com\",\n \"prod\": \"https://api.example.com\"\n },\n \"mongoUri\": {\n \"local\": \"mongodb://localhost:27020/app\",\n \"dev\": \"mongodb://dev.mongo.example.com:27017/app\",\n \"prod\": \"mongodb://mongo.example.com:27017/app\"\n },\n \"logLevel\": {\n \"default\": \"info\",\n \"local\": \"debug\"\n }\n}\n```\n\nAfter installing [single-config](https://www.npmjs.com/package/single-config) and running the `buildconfig` command:\n\n```shell:unstyled\nnpm i single-config -g\nbuildconfig --env=local\n```\n\nIt will generate a `config.js` module exporting an object with all the properties resolved to the \"local\" environment:\n\n```js\n// This file was automatically generated at 2021-07-18T15:46:04.304Z\nmodule.exports = {\n \"env\": \"local\",\n \"apiHost\": \"https://localhost:8080\",\n \"mongoUri\": \"mongodb://localhost:27020/app\",\n \"logLevel\": \"debug\"\n};\n```\n\nThe following diagram depicts the build logic:\n\n\u003cImage\n src=\"/images/single-config/single-config-diagram.png\"\n alt=\"single-config Diagram\"\n newWindow=\"true\"\n/\u003e\n","urlPath":"/managing-multi-environment-configurations-in-nodejs"},{"__metadata":{"id":"content/pages/posts/2017-10-20-indexing-and-searching-arbitrary-json-data-using-elasticsearch.md","source":"sourcebit-source-filesystem","sourceName":"pages","sourcePath":"/opt/build/repo/content/pages","relSourcePath":"posts/2017-10-20-indexing-and-searching-arbitrary-json-data-using-elasticsearch.md","relProjectPath":"content/pages/posts/2017-10-20-indexing-and-searching-arbitrary-json-data-using-elasticsearch.md"}
1,"layout":"post","title":"Indexing and Searching Arbitrary JSON Data using Elasticsearch","slug":"indexing-and-searching-arbitrary-json-data-using-elasticsearch","date":"2017-10-20","tags":["elasticsearch","indexing","data","json","searching","arbitrary","random","dynamic","strict","mapping"],"description":"Indexing arbitrary JSON data, including nested arrays and objects, into Elasticsearch, without increasing type mapping.","excerpt":"In this post, I would like to show you how to create an Elasticsearch index to index arbitrary JSON data, including data with nested arrays and objects. All this, without exploding the index type mapping with arbitrary properties originating from the indexed data. Nevertheless, the indexed data will still be searchable by any fields, including nested fields, by specifying their path in a \"dot\" notation format. In addition, I will show how to use Elasticsearch aggregations to fetch a list of all the available fields and their respective types of indexed data.\n","markdown_content":"\nIf you have ever worked with Elasticsearch, then you are probably familiar with one of the most important features of Elasticsearch - the [Dynamic Field Mapping][dynamic_field_mapping]:\n\n\u003e By default, when a previously unseen field is found in a document, Elasticsearch will add the new field to the type mapping.\n\nTherefore, if you need to index documents with a high similarity between their field names (dense data), including field types, then this default behavior may be precisely what you want. However, if your documents have a high variation of field names (sparse data) or have the same field names but different types, you will need a different approach.\n\nIn this post, I would like to show you how to create an Elasticsearch index to index arbitrary JSON data, including data with nested arrays and objects. All this, without exploding the index type mapping with arbitrary properties originating from the indexed data. Nevertheless, the indexed data will still be searchable by any fields, including nested fields, by specifying their path in a \"dot\" notation format. In addition, I will show how to use Elasticsearch aggregations to fetch a list of all the available fields and their respective types of indexed data.\n\n\u003cImage \n src=\"/images/elasticsearch/elasticsearch-arbitrary-data.png\" \n title=\"Indexing and Searching Arbitrary Data in Elasticsearch\"\n newWindow=\"true\"\n/\u003e\n\n\n## Dynamic or Strict?\n\nBefore we begin, let's see how the default [Dynamic field mapping][dynamic_field_mapping] works and what happens when we try to index arbitrary JSON documents. For example, let's try to index the following document into `my_index` index under `my_type` type:\n\n**Request**:\n\n`POST /my_index/my_type`\n\n```json\n{\n \"data\": {\n \"user\": \"smnh\",\n \"tags\": [\"elastic\", \"search\"],\n \"elasticsearch\": {\n \"version\": 5.6,\n \"currentVersion\": true\n }\n }\n}\n```\n\n**Response**:\n\n```json\n{\n \"_index\": \"my_index\",\n \"_type\": \"my_type\",\n \"_id\": \"AV8X9RqXF11pl6w7kdPg\",\n \"_version\": 1,\n \"result\": \"created\",\n \"_shards\": { ... },\n \"created\": true\n}\n```\n\nDue to [Automatic Index Creation][index_creation] and [Dynamic Mapping,][dynamic_mapping] Elasticsearch creates both `my_index` index and `my_type` type with appropriate mapping. We can get the created mapping by executing the following API request:\n\n**Request**:\n\n```plain\nGET /my_index/_mapping/my_type\n```\n\n**Response**:\n\n```json\n{\n \"my_index\": {\n \"mappings\": {\n \"my_type\": {\n \"properties\": {\n \"data\": {\n \"properties\": {\n \"elasticsearch\": {\n \"properties\": {\n \"currentVersion\": {\n \"type\": \"boolean\"\n },\n \"version\": {\n \"type\": \"float\"\n }\n }\n },\n \"tags\": {\n \"type\": \"text\",\n \"fields\": {\n \"keyword\": {\n \"type\": \"keyword\",\n \"ignore_above\": 256\n }\n }\n },\n \"user\": {\n \"type\": \"text\",\n \"fields\": {\n \"keyword\": {\n \"type\": \"keyword\",\n \"ignore_above\": 256\n }\n }\n }\n }\n }\n }\n }\n }\n }\n}\n```\n\nAs you can see, Elasticsearch created an [Object datatype][object_datatype] with three properties, one of which, the `elasticsearch`, is a
1nested Object datatype itself. Trying to index more documents with other fields will extend this mapping, eventually making it unreasonably huge. Moreover, indexing a new document with a field already used with a different type will result in an exception. For example, let's try to index a new document, but this time, instead of using `float` type for the `data.elasticsearch.version`, we will use a `text` type:\n\n`POST /my_index/my_type`:\n\n```json\n{\n \"data\": {\n \"elasticsearch\": {\n \"version\": \"6.x\"\n }\n }\n}\n```\n\n**Response**:\n\n```json\n{\n \"error\": {\n \"root_cause\": [\n {\n \"type\": \"mapper_parsing_exception\",\n \"reason\": \"failed to parse [data.elasticsearch.version]\"\n }\n ],\n \"type\": \"mapper_parsing_exception\",\n \"reason\": \"failed to parse [data.elasticsearch.version]\",\n \"caused_by\": {\n \"type\": \"number_format_exception\",\n \"reason\": \"For input string: \\\"6.x\\\"\"\n }\n },\n \"status\": 400\n}\n```\n\nBecause we have already indexed one document with a `float` value for the `data.elasticsearch.version` field, we can not index another document with a different type for the same field. A similar problem will occur if we try to index a document with an array field of different types (assuming [coercion][coerce] is turned off or can not be applied).\n\n\n## Creating an Index\n\nAs you have already guessed, we need to turn off the [Dynamic field mapping][dynamic_field_mapping] to prevent index type mappings from growing with every newly introduced field. And with a bit of effort, we can define an index mapping that will allow us to index documents with high variation of field names, including documents with fields of different types or fields with arrays of different value types.\n\nThe idea for this solution mainly comes from [this elastic.co blog post][great_mapping_refactoring]. The idea is to create a list of objects\nwith predefined fields holding the **flattened** keys and values of the original data. Continuing our previous example, instead of indexing the original document, we could index the following document:\n\n```json\n{\n \"data\": { ... original document ... }\n \"flatData\": [\n {\n \"key\": \"user\",\n \"type\": \"string\",\n \"key_type\": \"user.string\",\n \"value_string\": \"smnh\"\n },\n {\n \"key\": \"tags\",\n \"type\": \"string\",\n \"key_type\": \"tags.string\",\n \"value_string\": [\"elastic\", \"search\"]\n },\n {\n \"key\": \"elasticsearch.version\",\n \"type\": \"float\",\n \"key_type\": \"elasticsearch.version.float\",\n \"value_float\": 5.6\n },\n {\n \"key\": \"elasticsearch.currentVersion\",\n \"type\": \"boolean\",\n \"key_type\": \"elasticsearch.currentVersion.boolean\",\n \"value_boolean\": true\n }\n ]\n}\n```\n\nIn this document, every object in the `flatData` array represents a leaf node in the original document and has the following fields:\n\n- `key`: the path of the field in the original document\n- `type`: the type of the field value\n- `key_type`: the `key` and the `type` concatenated by a `\".\"` (for faster aggregations)\n- `value_{type}`: the field value. The name of this field is created by concatenating the string `\"value_\"` with the value of the `type` field (e.g., `value_string`, `value_float`, `value_long`, etc.).\n\nNotes:\n\n- The original document is included inside the `data` field to be stored and returned with the `_source` field, although it will not be indexed. On the other hand, the `flatData` field will be indexed but not stored inside the `_source` field.\n- Had the `tags` array included values of different types, then its values would have been separated and grouped within objects by their types.\n\nTo index a document of this type, we will need to create an index with an appropriate mapping. Assuming our new index will be called `my_index` and our document type will be called `my_type`, the index creation request will look like this:\n\n```json:showLineNumbers:highlightLines(6,11,13,17,18,21,38,56)\nPUT /my_index\n\n{\n \"settings\": {\n \"index\": {\n \"mapper.dynamic\": false\n }\n },\n \"mappings\": {\n \"my_type\": {\n \"dynamic\": \"strict\",\n \"_source\": {\n \"excludes\": [\"flatData.*\"]\n },\n \"properties\": {\n \"data\": {\n \"type\": \"object\",\n \"enabled\": false\n },\n \"flatData\": {\n \"type\": \"nested\",\n \"properties\": {\n \"key\": {\n \"type\": \"keyword\",\n \"include_in_all\": false\n },\n \"type\": {\n \t\"type\": \"keyword\",\n \t\"include_in_all\": false\n },\n \"key_type\": {\n \"type\": \"keyword\",\n \"include_in_all\": false\n },\n \"value_string\": {\n \"type\": \"text\",\n \"fields\": {\n \"keyword\": {\n \"type\": \"keyword\",\n \"ignore_above\": 256\n }\n }\n },\n \"value_boolean\": {\n \"type\": \"boolean\"\n },\n \"value_date\": {\n \"type\": \"date\"\n },\n \"value_long\": {\n \"type\": \"long\"\n },\n \"value_float\": {\n \"type\": \"float\"\n },\n \"value_null\": {\n \"type\": \"boolean\",\n \"null_value\": false,\n \"include_in_all\": false\n }\n }\n }\n }\n }\n }\n}\n```\n\n- The `settings.index.mapping.dynamic` is `false` to [disable automatic type creation][disabling_automatic_type_creation]. It is not necessary for the purpose of this post, but I do like to make things as strict as possible.\n- The `mappings.my_type.dynamic` is `strict` to disable the automatic creation of properties on `my_type` type. This is how we [turn off the Dynamic field mapping][dynamic].\n- The `mappings.my_type._source.excludes` is set to `[\"flatData.*\"]` to ensure that the `flatData` object and its flattened fields will not be included in the stored `_source` object. The `_source` already includes the original document inside the `data` field, so there is no reason to store this data twice.\n- The `data` field that stores the original document is of the [Object datatype][object_datatype]. The `enabled` flag of this property is set to `false` to ensure that it will be completely ignored and therefore will not be parsed and indexed. Although it will be stored inside the document's `_source` field.\n- The `flatData` object is of [Nested datatype][nested_datatype] (click the link to learn why this property must be of this type). As previously seen, this object is derived from the original data by flattening its keys. The flattening procedure is described
1in the following section.\n- The `keyword` field is a [multi-field][multi_fields] having the `flatData.value_string.keyword` path and the `keyword` type. This allows analyzing and indexing the value of the `flatData.value_string` as a `keyword` for exact value searches.\n- The `value_null` field is a special field for storing `null` values. [Elasticsearch does not store **null** values][dealing_with_null_values], therefore in order to be able to query for `null` values, we need to define a separate field with [null_value][null_value] parameter. In our case, the `null` value will be represented by `flatData.value_null` of a `boolean` type with a `false` value.\n\n\n## Flattening the Data\n\nThe data flattening procedure is not complicated. It is less than 100 lines of code. The following Gist includes the `flattenData` function. This function receives an object and flattens it into an array of objects, having the same format as we have seen before. The following section explains the high-level logic behind this function.\n\n\u003cGist id=\"smnh/30f96028511e1440b7b02ea559858af4\" /\u003e\n\n\n### Every value is an array\n\nElasticsearch indexes all document fields as [multi-value fields][multivalue_fields]. Therefore [it does not have a dedicated **array** type][array]. As a matter of fact, every type is an array of values of that type. Thus, the flattening process does not indicate the presence of arrays to the field path (i.e., the `key` property).\n\nFor example, given the following data:\n\n```json\n{\n \"tags\": [\"elastic\", [\"search\"]]\n}\n```\n\nThe flattened data will look like this:\n\n```json\n[{\n \"key\": \"tags\",\n \"type\": \"string\",\n \"key_type\": \"tags.string\",\n \"value_string\": [\"elastic\", \"search\"]\n}]\n```\n\nThe `key` property is `tags`, the `type` property is a `string`, and the `value_string` is an array of strings. Hence, there is no indication that the original value had a nested array. Let's take a look at another example:\n\n```json\n{\n \"tags\": \"elastic search\"\n}\n```\n\nThis data will be flattened into a similar object:\n\n```json\n[{\n \"key\": \"tags\",\n \"type\": \"string\",\n \"key_type\": \"tags.string\",\n \"value_string\": \"elastic search\"\n}]\n```\n\n\u003e **Note**: as noted previously, from the Elasticsearch perspective, a single value is semantically identical to an array with a single element. Therefore we could wrap the `\"elastic search\"` string with an array and get the exact same result.\n\nIn the last example, although its `value_string` holds a single string instead of an array of two separate strings, Elasticsearch will analyze and index this document exactly in the same way as it will do with the document from the previous example. The only exception is that the former document's `flatData.value_string.keyword` field will have two separate terms. In contrast, the latter document will have only one term - the original `\"elastic search\"` string.\n\n\n### Grouping fields values by path and type\n\nField values having the same paths and types are grouped into single arrays. This perfectly aligns with how Elasticsearch [indexes arrays of nested objects][object_arrays].\n\nFor example, the following data:\n\n```json\n{\n \"versions\": [\n {\n \"version\": \"4.4\",\n \"name\": \"KitKat\"\n },\n {\n \"version\": \"5.0\",\n \"name\": \"Lollipop\"\n },\n {\n \"version\": \"6.0\",\n \"name\": \"Marshmallow\"\n }\n ]\n}\n```\n\nWill be flattened as:\n\n```json\n[\n {\n \"key\": \"versions.version\",\n \"type\": \"string\",\n \"key_type\": \"versions.version.string\",\n \"value_string\": [\"4.4\", \"5.0\", \"6.0\"]\n },\n {\n \"key\": \"versions.name\",\n \"type\": \"string\",\n \"key_type\": \"versions.name.string\",\n \"value_string\": [\"KitKat\", \"Lollipop\", \"Marshmallow\"]\n }\n]\n```\n\n\n### Splitting arrays by paths and types\n\nAs opposed to the previous rule, arrays with multi-type values, or field values with the same paths but different types, will be split into separate arrays grouped by type.\n\n```json\n{\n \"tags\": [\"elastic\", \"search\", 5.5, 5.6]\n}\n```\n\n```json\n[\n {\n \"key\": \"tags\",\n \"type\": \"string\",\n \"key_type\": \"tags.string\",\n \"value_string\": [\"elastic\", \"search\"]\n },\n {\n \"key\": \"tags\",\n \"type\": \"float\",\n \"key_type\": \"tags.float\",\n \"value_float\": [5.5, 5.6]\n }\n]\n```\n\n\n### Scalar values have empty keys\n\nIf the `data` is a scalar value or is an array of scalar values, the `key` property for these values will be an empty string.\n\nFor example:\n\n```json\n[\"elastic\", {\"search\": \"is great\"}]\n```\n\nWill be flattened into:\n\n```json\n[\n {\n \"key\": \"\",\n \"type\": \"string\",\n \"key_type\": \".string\",\n \"value_string\": [\"elastic\"]\n },\n {\n \"key\": \"search\",\n \"type\": \"string\",\n \"key_type\": \"search.string\",\n \"value_string\": [\"is great\"]\n }\n]\n```\n\n\n### Extensive example\n\n**data**:\n\n```javascript\nlet flatData = flatten
1Data({\n \"key1\": \"value1\",\n \"key2\": true,\n \"key3\": {\n \"key3_1\": 1,\n \"key3_2\": 2.2,\n \"key3_3\": \"2015-01-01\"\n },\n \"key4\": [\n \"value2\", \"value3\", 4, 5,\n {\"key7\": \"value4\"},\n {\"key7\": \"value5\"},\n {\"key7\": 6.1},\n {\"key7\": [\"value6\", 6.2]}\n ]\n});\n```\n\n**flatData**:\n\n```json\n[\n {\n \"key\": \"key1\",\n \"type\": \"string\",\n \"key_type\": \"key1.string\",\n \"value_string\": \"value1\"\n },\n {\n \"key\": \"key2\",\n \"type\": \"boolean\",\n \"key_type\": \"key2.boolean\",\n \"value_boolean\": true\n },\n {\n \"key\": \"key3.key3_1\",\n \"type\": \"long\",\n \"key_type\": \"key3.key3_1.long\",\n \"value_long\": 1\n },\n {\n \"key\": \"key3.key3_2\",\n \"type\": \"float\",\n \"key_type\": \"key3.key3_2.float\",\n \"value_float\": 2.2\n },\n {\n \"key\": \"key3.key3_3\",\n \"type\": \"date\",\n \"key_type\": \"key3.key3_3.date\",\n \"value_date\": \"2015-01-01T00:00:00.000Z\"\n },\n {\n \"key\": \"key4\",\n \"type\": \"string\",\n \"key_type\": \"key4.string\",\n \"value_string\": [\"value2\", \"value3\"]\n },\n {\n \"key\": \"key4\",\n \"type\": \"long\",\n \"key_type\": \"key4.long\",\n \"value_long\": [4, 5]\n },\n {\n \"key\": \"key4.key7\",\n \"type\": \"string\",\n \"key_type\": \"key4.key7.string\",\n \"value_string\": [\"value4\", \"value5\", \"value6\"]\n },\n {\n \"key\": \"key4.key7\",\n \"type\": \"float\",\n \"key_type\": \"key4.key7.float\",\n \"value_float\": [6.1, 6.2]\n }\n]\n```\n\n\n## Getting the Available Field Names and Types\n\nHaving our arbitrary data indexed, we now want to know which fields and types exist in the index. This kind of information may be used to build queries and execute searches. For example, we can create a dynamic user interface that allows creating a query by selecting a field name from the list of available field names. Then, based on the selected field name, the UI can present a dropdown box with all the available types for the selected field. And based on the selected field type, it can then present a dropdown box with an operator selector and an input field to enter a value.\n\n\u003cImage\n src=\"/images/elasticsearch/elasticsearch-arbitrary-data-query-builder.png\"\n title=\"Building Elasticsearch query using available field names and types\"\n newWindow=\"true\"\n/\u003e\n\nTo get the available fields and their types in the indexed documents, we can use [Elasticsearch Aggregations][search_aggregations]. Because we have indexed every field with `key` and `type` properties, we can aggregate all the needed data by using three-level deep nested aggregation:\n\nA [Terms Aggregation][terms_aggregation] over the `type` field, nested in another Terms Aggregation over the `key` field, nested inside another [Nested Aggregation][nested_aggregation] over the `flatData` field.\n\n\u003e If the indexed documents do not have a high variation of types per field, instead of having `type` aggregation nested inside `key` aggregation, a single `key_type` aggregation may be used. The returned `key_type` values should be split by the last `.` (dot character) to get the `key` and `type` of the fields.\n\n**Example**:\n\n`POST /my_index/my_type/_search`:\n\n```json\n{\n\t\"size\": 0,\n\t\"aggs\": {\n \"dataFields\": {\n \"nested\": {\n \"path\": \"flatData\"\n },\n \"aggs\": {\n \"keys\": {\n \"terms\": {\n \"field\": \"flatData.key\",\n \"size\": 50\n },\n \"aggs\": {\n \"types\": {\n \"terms\": {\n \"field\": \"flatData.type\",\n \"size\": 10\n }\n }\n }\n }\n }\n }\n\t}\n}\n```\n\n**Result**:\n\nThe aggregation result is pretty extensive, so I've cleaned some non-relevant properties, but technically it will have the following structure:\n\n```json\n{\n ...\n \"aggregations\": {\n \"dataFields\": {\n \"keys\": {\n \"doc_count_error_upper_bound\": 0,\n \"sum_other_doc_count\": 0,\n \"buckets\": [\n {\n \"key\": \"user\",\n \"types\": {\n \"buckets\": [\n {\n \"key\": \"string\",\n ...\n }\n ],\n ...\n },\n ...\n },\n {\n \"key\": \"elasticsearch.version\",\n \"types\": {\n \"buckets\": [\n {\n \"key\": \"string\",\n ...\n },\n {\n \"key\": \"float\",\n ...\n }\n ],\n ...\n },\n ...\n },\n ...\n ]\n },\n ...\n }\n }\n}\n```\n\n\u003e **Note**: if the `sum_other_doc_count` is greater than zero, it means that some of the fields were not returned. In this case, the `size` of the `flatData.key` aggregation should be bigger to ensure that all of the fields will be returned.\n\nWe can wrap this logic inside a single function which will return all the available fields in a \"dot\" notation format:\n\n```javascript\ngetDataFields().then(result =\u003e {});\n```\n\nThe `result` will be an array of objects having two fields:\n\n- `key`: field path in a \"dot\" notation format\n- `types`: array of all the types this field might have in the same document or across multiple documents.\n\n```json\n[\n {\"key\": \"user\", \"types\": [\"string\"]},\n {\"key\": \"tags\", \"types\": [\"string\", \"float\"]},\n {\"key\": \"elasticsearch.version\", \"types\": [\"string\", \"float\"]},\n {\"key\": \"elasticsearch.currentVersion\", \"types\": [\"boolean\"]},\n]\n```\n\n### Searching by Field Names and Types\n\nNow that we have all the field names and types in the indexed data, we can create and execute search queries. If we want to search by a specific field, every search query should contain a `bool` query with a `must` clause having at least two queries. First is a `term` query for matching the field name (the `flatData.key` property), and the second is any other query for matching the actual value. It may be a `match`, `term`, `range,` or any other query that may fit the specific case.\n\nFor example, if we would like to find all the documents having the `elasticsearch.version` field of type `string` equal to `6.x`, we would execute the following query:\n\n```json\n{\n \"query\": {\n \"nested\": {\n \"path\": \"flatData\",\n \"query\": {\n \"bool\": {\n \"must\": [\n {\"term\": {\"flatData.key\": \"elasticsearch.version\"}}
1,\n {\"match\": {\"flatData.value_string\": \"6.x\"}}\n ]\n }\n }\n }\n }\n}\n```\n\n\n[dynamic_mapping]: https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-mapping.html\n[dynamic_field_mapping]: https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-field-mapping.html\n[dynamic]: https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic.html\n[index_creation]: https://www.elastic.co/guide/en/elasticsearch/reference/5.5/docs-index_.html#index-creation\n[object_datatype]: https://www.elastic.co/guide/en/elasticsearch/reference/current/object.html\n[great_mapping_refactoring]: https://www.elastic.co/blog/great-mapping-refactoring#conflicting-mappings\n[nested_datatype]: https://www.elastic.co/guide/en/elasticsearch/reference/5.5/nested.html\n[array]: https://www.elastic.co/guide/en/elasticsearch/reference/current/array.html\n[search_aggregations]: https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html\n[nested_aggregation]: https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-nested-aggregation.html\n[terms_aggregation]: https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html\n[coerce]: https://www.elastic.co/guide/en/elasticsearch/reference/current/coerce.html#coerce\n[disabling_automatic_type_creation]: https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-mapping.html#_disabling_automatic_type_creation\n[multivalue_fields]: https://www.elastic.co/guide/en/elasticsearch/guide/current/_multivalue_fields_2.html\n[object_arrays]: https://www.elastic.co/guide/en/elasticsearch/guide/current/complex-core-fields.html#object-arrays\n[multi_fields]: https://www.elastic.co/guide/en/elasticsearch/reference/current/multi-fields.html\n[dealing_with_null_values]: https://www.elastic.co/guide/en/elasticsearch/guide/current/_dealing_with_null_values.html\n[null_value]: https://www.elastic.co/guide/en/elasticsearch/reference/current/null-value.html\n","urlPath":"/indexing-and-searching-arbitrary-json-data-using-elasticsearch"},{"__metadata":{"id":"content/pages/posts/2015-02-07-extending-events-and-attributes-of-the-inherited-backbone-views.md","source":"sourcebit-source-filesystem","sourceName":"pages","sourcePath":"/opt/build/repo/content/pages","relSourcePath":"posts/2015-02-07-extending-events-and-attributes-of-the-inherited-backbone-views.md","relProjectPath":"content/pages/posts/2015-02-07-extending-events-and-attributes-of-the-inherited-backbone-views.md"},"layout":"post","title":"Extending events and properties of Backbone views","slug":"extending-events-and-properties-of-backbone-views","date":"2015-02-07","tags":["backbone","javascript","inheritance","merging","extending","properties","class","superclass","options"],"description":"Extending and merging properties of Backbone views","excerpt":"When extending Backbone views using the [Backbone.View.extend](https://backbonejs.org/#View-extend) method, the properties passed to the `extend` method will override properties defined in the super view. For example, if the super view has an [`events`](https://backbonejs.org/#View-events) hash, any `events` passed to the `extend` method will override super view's events. Sometimes this might be a desirable result. However, usually when we want to \"extend\" something we want to add extra functionality rather redefining it existing one.\n","jsFiles":["/scripts/backbone-views/jquery-2.1.3.min.js","/scripts/backbone-views/underscore-min.js","/scripts/backbone-views/transition.min.js","/scripts/backbone-views/backbone-min.js","/scripts/backbone-views/extending-backbone-views.js"],"markdown_content":"\nI like Backbone Views because they introduce a conventional way to wrap HTML elements and their presentation logic in a single JavaScript class. Thus, allowing better organization and reuse of my application views. In addition to that, by utilizing JavaScript prototypical inheritance, JavaScript classes can be further extended to allow even better code reuse. But sometimes, when I extend `Backbone.View` classes, I want to \"extend\" super view's functionality instead of overriding it.\n\nWhen extending Backbone views using the [Backbone.View.extend](https://backbonejs.org/#View-extend) method, the properties passed to the `extend` method will override properties defined in the super view. For example, if the super view has an [`events`](https://backbonejs.org/#View-events) hash, any `events` passed to the `extend` method will override super view's events. Sometimes this might be a desirable result. However, usually when we want to \"extend\" something we want to add extra functionality rather redefining it existing one.\n\nFor example, let's create a modal view. This view will be responsible for showing a modal box with an optional \"close\" button in its top-right corner and a semi-transparent overlay covering the underlying web page. The overlay can be configured to close the model if it's clicked. The following code presents the possible implementation of the `show` method, the `events`, and the `defaultOptions` attribute in our `ModalView` class:\n\n```javascript\nvar ModalView = Backbone.View.extend({\n events: {\n \"click .modalViewCurtain\": \"onCurtainClick\",\n \"click .modalViewCloseButton\": \"onCloseButtonClick\"\n },\n defaultOptions: {\n \"showCloseButton\": true,\n \"closeWhenCurtainClicked\": true,\n \"animation\": \"bubble\",\n \"width\": 300,\n \"height\"
1: 140\n },\n show: function(title, content, options) {\n this.options = _.defaults({}, options || {}, this.defaultOptions);\n // Rest of the code that shows the modal\n },\n template: _.template( /* ... */ ),\n initialize: function() { /* ... */ },\n render: function() { /* ... */ },\n hide: function() { /* ... */ },\n onCurtainClick: function() {/* ... */ },\n onCloseButtonClick: function() { /* ... */ }\n});\n\n$(\"#modalViewDemo\").on(\"click\", function() {\n (new ModalView()).show(\"This is ModalView\", \"Click on the overlay or the close button to close.\");\n});\n```\n\n\u003cButton id=\"modalViewDemo\" label=\"ModalView Demo\" color=\"yellow\" center=\"true\"/\u003e\n\nNow, assume that we want to extend our `ModalView` to something more specific, for example, a `DialogView`. In addition to `ModelView`'s behavior, we want our `DialogView` to have two additional buttons at the bottom - \"Ok\" and \"Cancel\". By default, the `DialogView` should not be closed when the overlay is clicked. Ideally, we would want to subclass the `ModalView` and specify only the new events introduced in this subclass and only the new and overridden default options. Like this:\n\n```javascript\nvar DialogView = ModalView.extend({\n events: {\n \"click .dialogViewPositiveButton\": \"onPositiveButtonClick\",\n \"click .dialogViewNegativeButton\": \"onNegativeButtonClick\"\n },\n defaultOptions: {\n \"closeWhenCurtainClicked\": false, // overridden property\n \"positiveButtonLabel\": \"Ok\", // new property\n \"negativeButtonLabel\": \"Cancel\" // new property\n },\n render: function() {\n ModalView.prototype.render.call(this);\n // Append \"Ok\" and \"Cancel\" buttons\n }\n});\n```\n\nDue to the JavaScript nature, the `events` and `defaultOptions` objects defined in the `ModalView` class will be completely overridden by the objects defined in the `DialogView` class, thus breaking the logic of its superclass. Generally, we can solve this problem by repeating all the events and default options inherited from the `ModalView` in the `DialogView`. But we won't do that because it is not a scalable or maintainable solution.\n\n## Backboneâs `__super__` and ECMAScript's `hasOwnProperty` to the rescue!\n\nLuckily, Backbone has a [`__super__`](https://backbonejs.org/docs/backbone.html#section-276) property defined on constructors of its subclasses, including the `View` class. We can use this property to traverse the prototype chain of our views while augmenting `events` and `defaultOptions` objects defined in the inherited views. To decide which properties are \"owned\" by a specific prototype and not inherited from super-classes, we can use ECMAScriptâs `hasOwnProperty` method.\n\nFor example, we can override the constructor of our `ModalView` to achieve our goal:\n\n```javascript\nvar ModalView = Backbone.View.extend({\n constructor: function() {\n var prototype = this.constructor.prototype;\n\n this.events = {};\n this.defaultOptions = {};\n\n while (prototype) {\n if (prototype.hasOwnProperty(\"events\")) {\n _.defaults(this.events, prototype.events);\n }\n if (prototype.hasOwnProperty(\"defaultOptions\")) {\n _.defaults(this.defaultOptions, prototype.defaultOptions);\n }\n prototype = prototype.constructor.__super__;\n }\n\n Backbone.View.apply(this, arguments);\n },\n ...\n});\n\n$(\"#dialogViewDemo\").on(\"click\", function() {\n (new DialogView()).show(\"This is DialogView\", 'Click on \"Ok\", \"Cancel\" or close button to close. Clicking on the overlay will not close the DialogView by default');\n});\n```\n\nNow, any view that extends the `ModalView` will also extend its `events` and `defaultOptions` objects with its own.\n\n\u003cButton id=\"dialogViewDemo\" label=\"DialogView Demo\" color=\"yellow\" center=\"true\"/\u003e\n\n## What about string properties like `className`?\n\nBackbone Views also have `className` property which defines the class added to the view's root element. By tweaking our constructor, we can also aggregate the class names of the inherited views. And because `className` is a string and not an object, extending `className` is done by concatenating class names from the prototype chain.\n\n```javascript:showLineNumbers:highlightLines(7,16,17,18)\nvar ModalView = Backbone.View.extend({\n constructor: function() {\
1n var prototype = this.constructor.prototype;\n\n this.events = {};\n this.defaultOptions = {};\n this.className = \"\";\n\n while (prototype) {\n if (prototype.hasOwnProperty(\"events\")) {\n _.defaults(this.events, prototype.events);\n }\n if (prototype.hasOwnProperty(\"defaultOptions\")) {\n _.defaults(this.defaultOptions, prototype.defaultOptions);\n }\n if (prototype.hasOwnProperty(\"className\")) {\n this.className += \" \" + prototype.className;\n }\n prototype = prototype.constructor.__super__;\n }\n\n Backbone.View.apply(this, arguments);\n },\n ...\n});\n```\n\n## More generic approach\n\nLet's make it more generic by defining an `extendProperties` helper method that receives an object with property names as keys and methods that extend these properties as values. This method will traverse the prototype chain and extend the properties using the appropriate extension method. In addition, this method ensures that if a property wasn't defined in any of the inherited classes, including the inheriting class, then its value will remain `undefined`:\n\n```javascript\nvar ModalView = Backbone.View.extend({\n extendProperties: function(properties) {\n var prototype = this.constructor.prototype,\n propertyName, prototypeValue, extendMethod;\n \n while (prototype) {\n for (propertyName in properties) {\n if (prototype.hasOwnProperty(propertyName)) {\n prototypeValue = prototype[propertyName];\n extendMethod = properties[propertyName];\n if (!this.hasOwnProperty(propertyName)) {\n this[propertyName] = prototypeValue;\n } else if (_.isFunction(extendMethod)) {\n extendMethod.call(this, propertyName, prototypeValue);\n } else if (extendMethod === \"defaults\") {\n _.defaults(this[propertyName], prototypeValue);\n }\n }\n }\n prototype = prototype.constructor.__super__;\n }\n },\n constructor: function() {\n this.extendProperties({\n \"events\": \"defaults\",\n \"defaultOptions\": \"defaults\",\n \"className\": function(propertyName, prototypeValue) {\n this[propertyName] += \" \" + prototypeValue;\n }\n });\n \n Backbone.View.apply(this, arguments);\n },\n ...\n});\n```\n\n## What about properties defined as a function?\n\nBackbone allows properties such as `className` and `events` to be defined as functions to define their values at runtime. To support this feature we can tweak our while loop inside `extendProperties` method to use underscore `_.result` method like this:\n\n```javascript:showLineNumbers:highlightLines(8)\nvar ModalView = Backbone.View.extend({\n extendProperties: function(properties) {\n var prototype = this.constructor.prototype,\n propertyName, prototypeValue, extendMethod;\n while (prototype) {\n for (propertyName in properties) {\n if (prototype.hasOwnProperty(propertyName)) {\n prototypeValue = _.result(prototype, propertyName);\n extendMethod = properties[propertyName];\n if (!this.hasOwnProperty(propertyName)) {\n this[propertyName] = prototypeValue;\n } else if (_.isFunction(extendMethod)) {\n extendMethod.call(this, propertyName, prototypeValue);\n } else if (extendMethod === \"defaults\") {\n _.defaults(this[propertyName], prototypeValue);\n }\n }\n }\n prototype = prototype.constructor.__super__;\n }\n },\n ...\n});\n```\n\n## Even more generic approach\n\nWhat if we would like to add additional extendable properties to our `ModalView`? The
1n we would need to change the `ModalView`'s constructor every time we want to add a new property. This solution does not align with proper object-oriented design - a superclass shouldn't be aware of properties defined in its subclasses. Thus, to make a more generic approach, we will define the `extendableProperties` property on classes whose properties can be extended by subclasses. The value of `extendableProperties` will be an object with keys matching the property names and values defining the method to extend them. The `extendableProperties` property itself can be extended to list more properties. To make this approach generic, we will create a new base class called `BaseVeiw` with extension logic:\n\n```javascript\nvar BaseView = Backbone.View.extend({\n\n extendableProperties: {\n \"events\": \"defaults\",\n \"className\": function(propertyName, prototypeValue) {\n this[propertyName] += \" \" + prototypeValue;\n }\n },\n\n extendProperties: function(properties) {\n var propertyName, prototypeValue, extendMethod,\n prototype = this.constructor.prototype;\n\n while (prototype) {\n for (propertyName in properties) {\n if (properties.hasOwnProperty(propertyName) \u0026\u0026 prototype.hasOwnProperty(propertyName)) {\n prototypeValue = _.result(prototype, propertyName);\n extendMethod = properties[propertyName];\n if (!this.hasOwnProperty(propertyName)) {\n this[propertyName] = prototypeValue;\n } else if (_.isFunction(extendMethod)) {\n extendMethod.call(this, propertyName, prototypeValue);\n } else if (extendMethod === \"defaults\") {\n _.defaults(this[propertyName], prototypeValue);\n }\n }\n }\n prototype = prototype.constructor.__super__;\n }\n },\n\n constructor: function() {\n if (this.extendableProperties) {\n // First, extend the extendableProperties by collecting all the extendable properties\n // defined by classes in the prototype chain.\n this.extendProperties({\"extendableProperties\": \"defaults\"});\n\n // Now, extend all the properties defined in the final extendableProperties object\n this.extendProperties(this.extendableProperties);\n }\n\n Backbone.View.apply(this, arguments);\n }\n});\n\nvar ModalView = BaseView.extend({\n extendableProperties: {\n \"defaultOptions\": \"defaults\"\n },\n className: \"modalView\",\n events: {\n \"click .modalViewCurtain\": \"onCurtainClick\",\n \"click .modalViewCloseButton\": \"onCloseButtonClick\"\n },\n defaultOptions: {\n \"showCloseButton\": true,\n \"closeWhenCurtainClicked\": true,\n \"animation\": \"bubble\",\n \"width\": 300,\n \"height\": 140\n },\n ...\n});\n\nvar DialogView = ModalView.extend({\n className: \"dialogView\",\n events: {\n \"click .dialogViewPositiveButton\": \"onPositiveButtonClick\",\n \"click .dialogViewNegativeButton\": \"onNegativeButtonClick\"\n },\n defaultOptions: {\n \"closeWhenCurtainClicked\": false, // overridden property\n \"positiveButtonLabel\": \"Ok\", // new property\n \"negativeButtonLabel\": \"Cancel\" // new property\n },\n ...\n});\n```\n","urlPath":"/extending-events-and-properties-of-backbone-views"},{"__metadata":{"id":"content/pages/posts/2015-01-28-synchronizing-rotation-animation-between-the-keyboard-and-the-attached-view-part-2.md","source":"sourcebit-source-filesystem","sourceName":"pages","sourcePath":"/opt/build/repo/content/pages","relSourcePath":"posts/2015-01-28-synchronizing-rotation-animation-between-the-keyboard-and-the-attached-view-part-2.md","relProjectPath":"content/pages/posts/2015-01-28-synchronizing-rotation-animation-between-the-keyboard-and-the-attached-view-part-2.md"}
1,"layout":"post","title":"Synchronizing rotation animation between the keyboard and the attached view - Part 2","slug":"synchronizing-rotation-animation-between-the-keyboard-and-the-attached-view-part-2","date":"2015-01-28","tags":["ios","uiview","uiviewcontroller","keyboard","keyboardWillShow","keyboardWillHide","orientation"],"description":"Synchronizing rotation animation between the keyboard and the attached view in iOS without timers","excerpt":"In the \\[first part]\\[first\\_part] of this post, I have explained how to synchronize rotation animation in iOS between the virtual keyboard and the view floating above it. Many things have been changed since then, iOS 6, iOS 7, and now iOS 8, which is why I am writing the second part of that post. In short, Apple changed something in iOS 8, causing keyboard notifications observer methods to execute their code while animations are disabled. But only when notifications are posted due to an interface orientation change while the keyboard was visible.\n","markdown_content":"\nIn the [first part][first_part] of this post, I have explained how to synchronize rotation animation in iOS between the virtual keyboard and the view floating above it. Many things have been changed since then, iOS 6, iOS 7, and now iOS 8, which is why I am writing the second part of that post. In short, Apple changed something in iOS 8, causing keyboard notifications observer methods to execute their code while animations are disabled. But only when notifications are posted due to an interface orientation change while the keyboard was visible.\n\n## tl;dr\n\nIf you want the code that fixes this problem, use the Gist below. The code has a minimal set of methods your UIViewController should implement to create the proper animation effects when the keyboard is shown, hidden, rotated, and even when the QuickType bar is minimized or expanded. But if you want to understand what is happening there, I suggest you continue reading.\n\n\u003cGist id=\"smnh/e864896ba37bc4cfdce6\" /\u003e\n\n## The problem\n\nAs I've previously noted, the problem is that in iOS 8, the views that should animate in coordination with the keyboard rotation never animate.\n\n\u003ciframe width=\"560\" height=\"315\" src=\"https://www.youtube-nocookie.com/embed/xptEBy0cPkM?rel=0\u0026theme=light\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen style=\"margin:auto; display:block; margin-bottom: 20px; max-width: 100%;\"\u003e\u003c/iframe\u003e\n\nFirst, I thought that it was happening because I was using deprecated methods to update the views when the interface orientation was changed:\n\n- [`â willRotateToInterfaceOrientation:duration:`](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/index.html#//apple_ref/occ/instm/UIViewController/willRotateToInterfaceOrientation:duration:)\n- [`â willAnimateRotationToInterfaceOrientation:duration:`](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/index.html#//apple_ref/occ/instm/UIViewController/willAnimateRotationToInterfaceOrientation:duration:)\n- [`â didRotateFromInterfaceOrientation:`](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/index.html#//apple_ref/occ/instm/UIViewController/didRotateFromInterfaceOrientation:)\n\nSo, I've added the new [`viewWillTransitionToSize:withTransitionCoordinator:`](https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIContentContainer_Ref/index.html#//apple_ref/occ/intfm/UIContentContainer/viewWillTransitionToSize:withTransitionCoordinator:) method with the same logic I used in these three deprecated methods:\n\n```objectivec\n- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id\u003cUIViewControllerTransitionCoordinator\u003e)coordinator {\n [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];\n [coordinator animateAlongsideTransition:^(id\u003cUIViewControllerTransitionCoordinatorContext\u003e context) {\n self.animationDuration = [context transitionDuration];\n self.animationCurve = [context completionCurve];\n self.animatingRotation = YES;\n } completion:^(id\u003cUIViewControllerTransitionCoordinatorContext\u003e context) {\n self.animatingRotation = NO;\n }];\n}\n```\n\nIgnore the `self.animationDuration` and `self.animationCurve` properties for now.\n\nOf course, this method didn't help. Otherwise, I
1wouldn't write this post. But, the new order of the events appeared to be as follows (note: the deprecated methods were not called although they were defined):\n\n1. `viewWillTransitionToSize:withTransitionCoordinator:` method called\n2. transition coordinator's `animateAlongsideTransition:` block called\n3. `UIKeyboardWillHideNotification` notification posted\n4. `UIKeyboardWillShowNotification` notification posted\n5. transition coordinator `completion:` block called\n6. `UIKeyboardWillHideNotification` notification posted\n7. `UIKeyboardWillShowNotification` notification posted\n\nYep, `UIKeyboardWillHideNotification` and `UIKeyboardWillShowNotification` notifications were posted one after another in both cases. And as with previous iOS versions all keyboard notifications hold 0 values for `UIKeyboardAnimationCurveUserInfoKey` and `UIKeyboardAnimationDurationUserInfoKey` notification's userInfo keys. However, now we have these values from the transition coordinator context. We will return to this fact later.\n\nThen, I thought that maybe guys working on the UIKit just moved the code that posts keyboard notifications when orientation changes from the animation block that animates the keyboard and the view controller. So, I've tried to put the code that updates the views inside an animation block. It didn't help either. Then, I thought that maybe it was related to the layout constraints. And although I knew that layout constraints are only a higher abstraction layer to view frames and layout, changing the code to use layout constraints probably won't help. I've tried it anyway. And as I was guessing, it didn't help. I continued to struggle with it couple more hours until I suddenly asked myself - \"Why do my views aren't animating even when I have changed their frames inside an animation block in the first place?\". That was the turning point question.\n\nIf you strip down the problem to a simple question like that, asking yourself why a basic thing such as animations does not work, you should look in a place that can hint you why. For iOS developers, this place is called \"UIKit Framework Reference\". And this is exactly what I did. Ten seconds later, I had the `areAnimationsEnabled` method name in my clipboard while looking for the right place to paste it into my code. Paste, build, run, and Voila! Just what I thought! iOS 8, unlike any others, disables animations when posting keyboard notifications due to a change in interface orientation â knowing that the solution was two steps away.\n\n## The solution\n\nAs you have already guessed, the solution is to enable the animations back. And if there is an `areAnimationsEnabled` method, then there must be a `setAnimationsEnabled:` method. This method is what will do the trick. But it's not the end of the story. In the [first part][first_part] of this post, I wrote that when keyboard notifications are posted due to the interface orientation rotation, views should be updated without animations because the notification is already posted from within an animation block. In iOS 8, this is not the case; we need to set up our own animations. But to synchronize animations, our views need to use the same animation characteristics used for the animation that rotates the keyboard and the view controller. Remember these `self.animationDuration` and `self.animationCurve` properties we've got from transition coordinator context? We will use them just for that:\n\n```objectivec\nif (!self.animatingRotation) {\n // Update the views as usual with animation using animationDuration and animationCurve received from keyboard notification.\n [self adjustViewsForKeyboardFrame:finalKeyboardFrame withAnimationDuration:animationDuration animationCurve:animationCurve];\n} else {\n if ([UIView areAnimationsEnabled]) {\n // Animations enabled, we are on iOS 7 and lower\n [self adjustViewsForKeyboardFrame:finalKeyboardFrame];\n } else {\n // Animations disabled, we are on iOS 8\n [UIView setAnimationsEnabled:YES];\n [self adjustViewsForKeyboardFrame:finalKeyboardFrame withAnimationDuration:self.animationDuration animationCurve:self.animationCurve];\n [UIView setAnimationsEnabled:NO];\n }\n}\n```\n\nI have disabled animations back to be polite. After all, guys at Apple disabled them for a reason.\n\n### What about the QuickType bar animation?\n\niOS 8 added the QuickType feature, adding a bar with word suggestions above the keyboard. Swiping this bar up and down animates its expansion and minimization. But if we will use our current solution, our view floating above that bar won't be animated when it is expanded or minimized. This happens because when the QuickType bar is expanded or minimized, the posted keyboard notifications hold 0 values for `UIKeyboardA
1nimationCurveUserInfoKey` and `UIKeyboardAnimationDurationUserInfoKey` notification's userInfo keys, just like it happens when the interface orientation is changed. On the other hand, putting the code that updates the floating view outside the animation block does the job. So, in this case, it behaves the same way as it behaves in iOS 7 when the interface orientation is changed. What a mess! Nevertheless, here is the code that fixes all issues:\n\n```objectivec\nif (!self.animatingRotation) {\n // Get the animation duration from keyboard notification info\n NSTimeInterval animationDuration = [[notificationInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];\n if (animationDuration == 0) {\n // On iOS8 if the animationDuration is 0 then it is the QuickType panel who triggered this keyboard notification so any view updates will be animated automatically\n [self adjustViewsForKeyboardFrame:finalKeyboardFrame];\n } else {\n UIViewAnimationCurve animationCurve = (UIViewAnimationCurve) [[notificationInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];\n [self adjustViewsForKeyboardFrame:finalKeyboardFrame withAnimationDuration:animationDuration animationCurve:animationCurve];\n }\n} else {\n if ([UIView areAnimationsEnabled]) {\n [self adjustViewsForKeyboardFrame:finalKeyboardFrame];\n } else {\n [UIView setAnimationsEnabled:YES];\n [self adjustViewsForKeyboardFrame:finalKeyboardFrame withAnimationDuration:self.animationDuration animationCurve:self.animationCurve];\n [UIView setAnimationsEnabled:NO];\n }\n}\n```\n\n\u003ciframe width=\"560\" height=\"315\" src=\"https://www.youtube-nocookie.com/embed/bbMYVEdodJQ\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen style=\"margin:auto; display:block; margin-bottom: 20px; max-width: 100%;\"\u003e\u003c/iframe\u003e\n\n## One more thing\n\nIn my original post, I used two different keyboard notifications: `UIKeyboardWillShowNotification` and `UIKeyboardWillHideNotification`. I found another keyboard notification called UIKeyboardWillChangeFrameNotification, which is called before each of the previous two. Therefore, instead of observing two notifications, we can observe only one.\n\nHere is the link to the Gist with the final solution.\n\n\u003cButton\n href=\"https://gist.github.com/smnh/e864896ba37bc4cfdce6\"\n large=\"true\"\n newWindow=\"true\"\n color=\"light\"\n bevel=\"true\"\n center=\"true\"\n icon=\"fab fa-github\"\n label=\"Github Gist\"\n/\u003e\n\n[first_part]: /synchronizing-rotation-animation-between-the-keyboard-and-the-attached-view \"First Part\"\n","urlPath":"/synchronizing-rotation-animation-between-the-keyboard-and-the-attached-view-part-2"},{"__metadata":{"id":"content/pages/posts/2014-04-22-hit-testing-in-ios.md","source":"sourcebit-source-filesystem","sourceName":"pages","sourcePath":"/opt/build/repo/content/pages","relSourcePath":"posts/2014-04-22-hit-testing-in-ios.md","relProjectPath":"content/pages/posts/2014-04-22-hit-testing-in-ios.md"},"layout":"post","title":"Hit-Testing in iOS","slug":"hit-testing-in-ios","date":"2014-04-22","tags":["hit-testing","ios","touch","uiview","tree traversal"],"description":"How hit-testing works in iOS","excerpt":"Hit-testing is the process of determining whether a point, such as a touch-point intersects with a given graphical object presented on the screen, such as `UIView`. iOS uses hit-testing to determine which `UIView` is the frontmost view under the userâs finger that should receive the touch event. It implements it by searching the view hierarchy using a reverse pre-order depth-first traversal algorithm.\n","markdown_content":"\nHit-testing is the process of determining whether a point, such as a touch-point intersects with a given graphical object presented on the screen, such as `UIView`. iOS uses hit-testing to determine which `UIView` is the frontmost view under the userâs finger that should receive the touch event. It implements it by searching the view hierarchy using a [reverse pre-order][reversePreOrder] depth-first traversal algorithm.\n\nBefore explaining how hit-testing works, it is important to understand when hit-testing is executed. The following diagram illustrates the high-level flow of a single touch, from the moment a finger touches the screen and until it is lifted from it:\n\n\u003cImage\n src=\"/images/hit-test/hit-test-touch-event-flow.png\"\n title=\"Touch event flow\"\n newWindow=\"true\"\n/\u003e\n\nAs illustrated in the diagram above, the hit-testing is executed every time a finger touches the screen. And, before any view or gesture recognizer receives the `UIEvent` object representing the event to which the touch belongs.\n\n\u003cAlert\u003e**NOTE**: for unknown reasons, the hit-testing is executed multiple times in a row. Yet, the determined hit-test view remains the same.\u003c/Alert\u003e\n\nAfter hit-testing completes and the frontmost view under the touch-point is determined, the hit-test view is associated with the `UITouch` object for all phases of the touch event sequence (i.e., began, moved, ended, or canceled). In addition to the hit-test view, any gesture recognizers attached to that view, and its ancestor views are associated with the `UITouch` object. Then, the hit-test view begins receiving the sequence of touch events.\n\nAn important thing to note is that even if the finger is moved from the hit-test view bounds over another view, the hit-test view continues receiving all the touch events until the end of the touch event sequence:\n\n\u003e âThe touch object is associated with its hit-test view for its lifetime, even if the touch later moves outside the view.â \n\u003e \u003ccite\u003e[Event Handling Guide for iOS, iOS Developer Library][eventhandlingguide]\u003c/cite\u003e\n\nAs mentioned earlier, the hit-testing uses a depth-first reverse pre-order traversal algorithm. In other words, the algorithm visits the root node first and then traverses its subtrees from higher to lower indexes. This kind of traversal allows reducing the number of traversal iterations and stopping the search process once the first deepest descendant view that contains the touch-point is found. This is possible since a subview is always rendered in front of its superview, and a sibling view is always rendered in front of its sibling views with a lower index into the subviews array. When multiple overlapping views contain a specific point, the deepest view in the rightmost subtree will be the frontmost view.\n\n\u003e âVisually, the content of a subview obscures all or part of the content of its parent view. Each superview stores its subviews in an ordered array, and the order in that array also affects the visibility of each subview. If two sibling subviews overlap each other, the one that was added last (or was moved to the end of the subview array) appears on top of the other.â \n\u003e \u003ccite\u003e[View Programming Guide for iOS, iOS Developer Library][viewprogrammingguide]\u003c/cite\u003e\n\nThe following diagram shows an example of a view hierarchy tree and its matching UI drawn on the screen. The arrangement of tree branches from left to right reflects the order of the subviews array.\n\n\u003cImage\n src=\"/images/hit-test/hit-test-view-hierarchy.png\"\n title=\"View hierarchy tree\"\n newWindow=\"true\"\n/\u003e\n\nAs it can be seen, âView Aâ and âView Bâ as well as their children, âView A.2â and âView B.1â, are overlapping. But since âView Bâ has a subview index higher than that of âView Aâ, âView Bâ and its subviews are rendered above âView Aâ and its subviews. Therefore, âView B.1â should be returned by hit-testing when the userâs finger touches âView B.1â in the area where it overlaps with âView A.2â.\n\nApplying depth-first traversal in reverse pre-order allows stopping the traversal once the first deepest descendant view that contains the touch-point is found:\n\n\u003cImage\n src=\"/images/hit-test/hit-test-depth-first-traversal.png\"\n title=\"View hierarchy depth-first traversal\"\n newWindow=\"true\"\n/\u003e\n\nThe traversal algorithm begins by sending the `hitTest:withEvent:` message to the `UIWindow`, which is the root view of the view hierarchy. The value returned from this method is the frontmost view containing the touch-point.\n\nFollowing flow-chart illustrates the hit-test logic.\n\n\u003cImage\n src=\"/images/hit-test/hit-test-flowchart.png\"\n title=\"Hit-test flow chart\"\n newWindow=\"true\"\n/\u003e\n\nAnd the following code shows possible implementation of the native `hitTest:withEvent:` method:\n\n```objectivec\n- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {\n if (!self.isUserInteractionEnabled || self.isHidden || self.alpha \u003c= 0.01) {\n return nil;\n }\n if ([self pointInside:point withEvent:event]) {\n for (UIView *subview in [self.subviews reverseObjectEnumerator]) {\n CGPoint convertedPoint = [subview convertPoint:point fromView:self];\n UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];\n if (hitTestView) {\n return hitTestView;\n }\n }\n return self;\n }\n return nil;\n}\n```\n\nThe `hitTest:withEvent:` method first checks if the view can receive the touch. A view is allowed to receive the touch if:\n\n- The view is not hidden: \n `self.hidden == NO`\n- The view has user interaction enabled: \n `self.userInteractionEnabled == YES`\n- The view has alpha level greater than 0.01: \n `self.alpha \u003e 0.01`\n- The view contains the point: \n `pointInside:withEvent: == YES`\n\nThen, if the view can receive the touch, this method traverses the receiverâs subtree by sending the `hitTest:withEvent:` message to its subviews from last to first until one of them returns non `nil` value. The first non `nil` value returned by one of the subviews is the frontmost view under the touch-point and is returned by the receiver. If all receiver subviews returned `nil` or the receiver has no subviews, the receiver returns itself.\n\nOtherwise, if the view is not allowed to receive the touch, this method returns `nil` without traversing the receiverâs subtree at all. Therefore, the hit-test process may not visit all the views in the view hierarchy.\n\n## Common use cases for overriding `hitTest:withEvent:`\n\nThe `hitTest:withEvent:` method could be overridden when the touch events intended to be handled by one view should be redirected to another view for all phases of that touch event sequence.\n\n\u003e Because the hit-test is executed before, and only before, the first touch event of the touch event sequence is sent to its receiver (the touch with the `UITouchPhaseBegan` phase), overriding `hitTest:withEvent:` to redirect events will redirect all touch events of that sequence.\n\n### Increasing view touch area\n\nOne use case which could justify overriding the `hitTest:withEvent:` method is when the touch area of a view should be larger than its bounds. For example, the following illustration shows a `UIView` having a size of 20x20. This size may be too small to handle nearby touches. Therefore, its touch area may be increased by 10 points in each direction by overriding the `hitTest:withEvent:` method:\n\n\u003cImage\n src=\"/images/hit-test/hit-test-increase-touch-area.png\"\n title=\"Increasing touch area\"\n newWindow=\"true\"\n/\u003e\n\n```objectivec\n- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {\n if (!self.isUserInteractionEnabled || self.isHidden || self.alpha \u003c= 0.01) {\n return nil;\n }\n\tCGRect touchRect = CGRectInset(self.bounds, -10, -10);\n if (CGRectContainsPoint(touchRect, point)) {\n for (UIView *subview in [self.subviews reverseObjectEnumerator]) {\n CGPoint convertedPoint = [subview convertPoint:point fromView:self];\n UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];\n if (hitTestView) {\n return hitTestView;\n }\n }\n return self;\n }\n return nil;\n}\n```\n\n\u003cAlert level=\"warn\"\u003eNote: for this view to be hit-tested correctly, the parent view's bounds should contain the desired touch area of its subview. If not, its `hitTest:withEvent:` method should also be overridden to include the desired touch area.\u003c/Alert\u003e\n\n### Passing touch events through to views below\n\nSometimes a view must ignore touch events and pass them through to the views below. For example, assume a transparent overlay view above all other application views. The overlay has some subviews in the form of controls and buttons, which should respond to touches normally. But touching the overlay somewhere else should pass the touch events to the views below the overlay. To accomplish this behavior, the overlay `hitTest:withEvent:` method could be overridden to return one of its subviews containing the touch-point and `nil` in all other cases, including the case when the overlay contains the touch-point:\n\n```objectivec\n- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {\n UIView *hitTestView = [super hitTest:point withEvent:event];\n if (hitTestView == self) {\n hitTestView = nil;\n }\n return hitTestView;\n}\n```\n\n### Passing touch events to subview\n\nAnother use case is when a parent view must redirect all touch events to its only child view. This behavior may be desired when the child view partially occupies its parent view but should respond to all touches occurring in its parent. For example, assume a carousel of images that consist of a parent view and a `UIScrollView` with `pagingEnabled` set to `YES` and `clipsToBounds` set to `NO` to create a carousel effect:\n\n\u003cImage\n src=\"/images/hit-test/hit-test-pass-touches-to-subviews.png\"\n title=\"Passing touch events to subview\"\n newWindow=\"true\"\n/\u003e\n\nTo make the `UIScrollView` to respond to touches occurring not only inside its own bounds, but also inside the bounds of its parent view, the parentâs `hitTest:withEvent:` method could be overridden in the following way:\n\n```objectivec\n- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {\n UIView *hitTestView = [super hitTest:point withEvent:event];\n if (hitTestView) {\n hitTestView = self.scrollView;\n }\n return hitTestView;\n}\n```\n\n[reversePreOrder]: https://en.wikipedia.org/wiki/Tree_traversal#Reverse_pre-order,_NRL \"Reverse pre-order tree traversal\"\n[eventhandlingguide]: https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/event_delivery_responder_chain/event_delivery_responder_chain.html#//apple_ref/doc/uid/TP40009541-CH4-SW4 \"Hit-Testing Returns the View Where a Touch Occurred\"\n[viewprogrammingguide]: https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/WindowsandViews/WindowsandViews.html#//apple_ref/doc/uid/TP40009503-CH2-SW24 \"View Hierarchies and Subview Management\"\n","urlPath":"/hit-testing-in-ios"},{"__metadata":{"id":"content/pages/posts/2013-09-21-synchronizing-rotation-animation-between-the-keyboard-and-the-attached-view.md","source":"sourcebit-source-filesystem","sourceName":"pages","sourcePath":"/opt/build/repo/content/pages","relSourcePath":"posts/2013-09-21-synchronizing-rotation-animation-between-the-keyboard-and-the-attached-view.md","relProjectPath":"content/pages/posts/2013-09-21-synchronizing-rotation-animation-between-the-keyboard-and-the-attached-view.md"}
1,"layout":"post","title":"Synchronizing rotation animation between the keyboard and the attached view","slug":"synchronizing-rotation-animation-between-the-keyboard-and-the-attached-view","date":"2013-09-21","tags":["ios","uiview","uiviewcontroller","keyboard","keyboardWillShow","keyboardWillHide","orientation"],"description":"Synchronizing rotation animation between the keyboard and the attached view in iOS","excerpt":"In this post, I would like to show how to make a `UIView` stay attached to the top of the iPhoneâs keyboard while the keyboard is animated. This solution works when the keyboard is animated while being presented or dismissed and when the iPhone is rotated and the interface orientation is changed.\n\n\u003ciframe width=\"560\" height=\"315\" title=\"Synchronizing rotation animation between the keyboard and the attached view in iOS\" src=\"https://www.youtube-nocookie.com/embed/Pl6OCwLfEWo\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen style=\"margin:auto; display:block; margin-bottom: 20px; max-width: 100%;\" /\u003e\n","markdown_content":"\nIn this post, I would like to show how to make a `UIView` stay attached to the top of the iPhoneâs keyboard while the keyboard is animated. This solution works when the keyboard is animated while being presented or dismissed and when the iPhone is rotated and the interface orientation is changed.\n\nThere could be found many answers explaining how to make a `UIView` stay attached to the keyboard while the keyboard is animated when presented or dismissed. However, I didn't find any examples explaining how to make `UIView` stay attached to the keyboard when the device is rotated, and the interface orientation is animated.\n\n\u003cAlert level=\"warn\"\u003e**\u003cIcon name=\"fas fa-info-circle\"/\u003e Note:** The solution explained in this post does not work in iOS 8. I've written the [second part](/synchronizing-rotation-animation-between-the-keyboard-and-the-attached-view-part-2) of this post that explains how to solve this problem in iOS 8.\u003c/Alert\u003e\n\n\n## tl;dr\n\nIf you want the code that fixes this problem, here is the [link](https://gist.github.com/smnh/e864896ba37bc4cfdce6) to the gist I've created with a working solution. The code has a minimal set of methods your `UIViewController` needs to create the proper animation effects when the keyboard is shown, hidden, rotated, and even when the QuickType bar is minimized or expanded. But if you want to learn what is happening behind the scenes, continue reading.\n\n## Basic Solution\n\nThe basic principle for solving the first problem requires observing keyboard notifications such as `UIKeyboardWillShowNotification` and `UIKeyboardWillHideNotification` and updating the appropriate views when these notifications are received. The keyboard notification object supplies the final keyboard position and its dimensions and keyboard animation properties such as duration and curve, allowing to perfectly synchronize the keyboard's appearance animation with the slide-up animation of the attached `UIView`.\n\nThe following example demonstrates the basic principle explained above:\n\n```objectivec\n- (void)viewWillAppear:(BOOL)animated {\n [super viewWillAppear:animated];\n\n [[NSNotificationCenter defaultCenter]\n addObserver:self selector:@selector(keyboardWillShow:)\n name:UIKeyboardWillShowNotification object:nil];\n [[NSNotificationCenter defaultCenter]\n addObserver:self selector:@selector(keyboardWillHide:)\n name:UIKeyboardWillHideNotification object:nil];\n}\n\n- (void)viewDidDisappear:(BOOL)animated {\n [super viewDidDisappear:animated];\n\n [[NSNotificationCenter defaultCenter]\n removeObserver:self name:UIKeyboardWillShowNotification object:nil];\n [[NSNotificationCenter defaultCenter]\n removeObserver:self name:UIKeyboardWillHideNotification object:nil];\n}\n\n- (void)keyboardWillShow:(NSNotification*)notification {\n [self adjustViewForKeyboardNotification:notification];\n}\n\n- (void)keyboardWillHide:(NSNotification*)notification {\n [self adjustViewForKeyboardNotification:notification];\n}\n\n- (void)adjustViewForKeyboardNotification:(NSNotification *)notification {\n NSDictionary *notificationInfo = [notification userInfo];\n\n // Get the end frame of the keyboard in screen coordinates.\n CGRect finalKeyboardFrame = [[notificationInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];\n // Get the animation curve and duration\n UIViewAnimationCurve animationCurve = (UIViewAnimationCurve) [[notificationInfo objectForKey:UIKeyboardA
1nimationCurveUserInfoKey] integerValue];\n NSTimeInterval animationDuration = [[notificationInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];\n\n // Convert the finalKeyboardFrame to view coordinates to take into account any rotation\n // factors applied to the windowâs contents as a result of interface orientation changes.\n finalKeyboardFrame = [self.view convertRect:finalKeyboardFrame fromView:self.view.window];\n\n // Calculate new position of the commentBar\n CGRect commentBarFrame = self.commentBar.frame;\n commentBarFrame.origin.y = finalKeyboardFrame.origin.y - commentBarFrame.size.height;\n\n // Update tableView height.\n CGRect tableViewFrame = self.tableView.frame;\n tableViewFrame.size.height = commentBarFrame.origin.y;\n\n // Animate view size synchronously with the appearance of the keyboard. \n [UIView beginAnimations:nil context:nil];\n [UIView setAnimationDuration:animationDuration];\n [UIView setAnimationCurve:animationCurve];\n [UIView setAnimationBeginsFromCurrentState:YES];\n\n self.commentBar.frame = commentBarFrame;\n self.tableView.frame = tableViewFrame;\n\n [UIView commitAnimations];\n}\n```\n\n## Advanced Solution\n\nHowever, If you are a hawk-eyed person who pays attention to the small details, then you must have noticed that even in popular applications such as Facebook Messenger and WhatsApp, the view with the text field attached to the top of the keyboard does not stay attached while the rotation of interface orientation is animated.\n\n\u003cImage\n src=\"/images/synchronizing-rotation-animation/whatsapp-rotated.png\"\n alt=\"WhatsApp rotation\"\n caption=\"Toolbar with textfield detached from keyboard while animating rotation in WhatsApp\"\n newWindow=\"true\"\n/\u003e\n\n\u003cImage\n src=\"/images/synchronizing-rotation-animation/facebook-messenger-rotated.png\"\n alt=\"Facebook Messenger rotation\"\n caption=\"Toolbar with textfield detached from keyboard while animating rotation in Facebook Messenger\"\n newWindow=\"true\"\n/\u003e\n\nThe following video is a slow-motion example of a non-synchronized animation between the keyboard and the view that does not stay attached to the keyboard while orientation is animated.\n\n\u003ciframe width=\"560\" height=\"315\" src=\"https://www.youtube-nocookie.com/embed/LjKqeETINGg\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen style=\"margin:auto; display:block; margin-bottom: 20px; max-width: 100%;\"\u003e\u003c/iframe\u003e\n\nThis detachment between the keyboard and the view happens because the keyboard is instantly dismissed and presented when the rotation animation starts. Subsequently, appropriate keyboard notifications are sent, and their notification observers set up two animations. But in fact, there is no need to set up these animations because UIKit already sets an animation to rotate the interface orientation.\n\nTo solve this problem, we need to prevent setting up any animations whenever the keyboard is dismissed or presented due to rotation of the interface orientation. Instead, we only need to update the position of appropriate views. For this purpose, we can utilize the following `UIViewController` methods:\n\n- [`â willRotateToInterfaceOrientation:duration:`](https://developer.apple.com/library/ios/documentation/uikit/reference/UIViewController_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40006926-CH3-SW28)\n- [`â willAnimateRotationToInterfaceOrientation:duration:`](https://developer.apple.com/library/ios/documentation/uikit/reference/UIViewController_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40006926-CH3-SW53)\n- [`â didRotateFromInterfaceOrientation:`](https://developer.apple.com/library/ios/documentation/uikit/reference/UIViewController_Class/Reference/Reference.html#//apple_ref/occ/instm/UIViewController/didRotateFromInterfaceOrientation:)\n\nWhen the device is rotated and the interface orientation is animated, the following events happen in order:\n\n1. `willRotateToInterfaceOrientation:duration:` method is called\n2. `UIKeyboardWillHideNotification` notification is sent\n3. `willAnimateRotationToInterfaceOrientation:duration:` method is called\n4. `UIKeyboardWillShowNotification` notification is sent\n5. `didRotateFromInterfaceOrientation:` method is called\n\nSo basically, we want to disable any custom animations between the first and the last method calls. And the simplest way to achieve that is by using a simple boolean flag.\n\nFollowing example demonstrates the solution explained above:\n\n```objectivec\n- (void)viewWillAp
1pear:(BOOL)animated {\n [super viewWillAppear:animated];\n\n [[NSNotificationCenter defaultCenter]\n addObserver:self selector:@selector(keyboardWillShow:)\n name:UIKeyboardWillShowNotification object:nil];\n [[NSNotificationCenter defaultCenter]\n addObserver:self selector:@selector(keyboardWillHide:)\n name:UIKeyboardWillHideNotification object:nil];\n}\n\n- (void)viewDidDisappear:(BOOL)animated {\n [super viewDidDisappear:animated];\n\n [[NSNotificationCenter defaultCenter]\n removeObserver:self name:UIKeyboardWillShowNotification object:nil];\n [[NSNotificationCenter defaultCenter]\n removeObserver:self name:UIKeyboardWillHideNotification object:nil];\n}\n\n// #1\n- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {\n [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];\n self.animatingRotation = YES;\n}\n// #3\n- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {\n [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];\n}\n// #5\n- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {\n [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];\n self.animatingRotation = NO;\n}\n// #4\n- (void)keyboardWillShow:(NSNotification*)notification {\n [self adjustViewForKeyboardNotification:notification];\n}\n// #2\n- (void)keyboardWillHide:(NSNotification*)notification {\n [self adjustViewForKeyboardNotification:notification];\n}\n\n- (void)adjustViewForKeyboardNotification:(NSNotification *)notification {\n NSDictionary *notificationInfo = [notification userInfo];\n\n // Get the end frame of the keyboard in screen coordinates.\n CGRect finalKeyboardFrame = [[notificationInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];\n\n // Convert the finalKeyboardFrame to view coordinates to take into account any rotation\n // factors applied to the windowâs contents as a result of interface orientation changes.\n finalKeyboardFrame = [self.view convertRect:finalKeyboardFrame fromView:self.view.window];\n\n // Calculate new position of the commentBar\n CGRect commentBarFrame = self.commentBar.frame;\n commentBarFrame.origin.y = finalKeyboardFrame.origin.y - commentBarFrame.size.height;\n\n // Update tableView height.\n CGRect tableViewFrame = self.tableView.frame;\n tableViewFrame.size.height = commentBarFrame.origin.y;\n\n if (!self.animatingRotation) {\n // Get the animation curve and duration\n UIViewAnimationCurve animationCurve = (UIViewAnimationCurve) [[notificationInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];\n NSTimeInterval animationDuration = [[notificationInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];\n\n // Animate view size synchronously with the appearance of the keyboard. \n [UIView beginAnimations:nil context:nil];\n [UIView setAnimationDuration:animationDuration];\n [UIView setAnimationCurve:animationCurve];\n [UIView setAnimationBeginsFromCurrentState:YES];\n\n self.commentBar.frame = commentBarFrame;\n self.tableView.frame = tableViewFrame;\n\n [UIView commitAnimations];\n } else {\n self.commentBar.frame = commentBarFrame;\n self.tableView.frame = tableViewFrame;\n }\n}\n```\n\nThe following video is a slow-motion example of the result.\n\n\u003ciframe width=\"560\" height=\"315\" src=\"https://www.youtube-nocookie.com/embed/Pl6OCwLfEWo\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen style=\"margin:auto; display:block; margin-bottom: 20px; max-width: 100%;\"\u003e\u003c/iframe\u003e\n\nReferences:\n\n- [Managing the Keyboard](https://developer.apple.com/library/ios/DOCUMENTATION/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html \"Managing the Keyboard\") (iOS Developer Library)\n- [How to make a UITextField move up when keyboard is present](https://stackoverflow.com/questions/1126726/how-to-make-a-uitextfield-move-up-when-keyboard-is-present \"How to make a UITextField move up when keyboard is present\") (Stackoverflow)\n","urlPath":"/synchronizing-rotation-animation-between-the-keyboard-and-the-attached-view"},{"__metadata":{"id":"content/pages/posts/2013-03-23-web-font-loading-detection-without-timers.md","source":"sourcebit-source-filesystem","sourceName":"pages","sourcePath":"/opt/build/repo/content/pages","relSourcePath":"posts/2013-03-23-web-font-loading-detection-without-timers.md","relProjectPath":"content/pages/posts/2013-03-23-web-font-loading-detection-without-timers.md"}
1,"layout":"post","title":"Web font loading detection, without timers","slug":"web-font-loading-detection-without-timers","date":"2013-03-23","tags":["JavaScript","webfonts","fonts","loading","fout","timers"],"description":"A javascript function that detects and notifies you when particular fonts have been loaded and are ready to be used on a webpage without using timers","excerpt":"A small JavaScript utility function that detects and notifies you when particular fonts have been loaded and are ready to be used on a webpage without using timers.\n","markdown_content":"\nAfter writing my [first post](/waiting-for-web-fonts-to-load \"Waiting for web fonts to load\") about detecting when particular font families have been loaded and rendered on a web page, I kept thinking about the efficiency of this approach. The fact that the time taken to download and render fonts on a web page depends on many factors, like the network latency and the processing power of the client device, makes it difficult to choose the right time interval for sampling the dimensions of elements. A long interval might introduce extra waiting time if the font was loaded just after a new timer iteration has been started. On the other hand, a short interval may introduce processing time overhead when trying to get the dimensions of elements by obtaining [layout triggering properties](https://gent.ilcore.com/2011/03/how-not-to-trigger-layout-in-webkit.html \"How (not) to trigger a layout in WebKit\") like `offsetWidth` and `offsetHeight`. So I kept thinking about how can I force the browser to notify me when a custom font has been loaded without sampling the dimensions of the element containing the loading font. And I found an event that can be used to solve this problem!\n\nIf you just want to use the FontLoader without reading this post navigate to the Github repo and follow the instructions in readme file:\n\n\u003cButton\n href=\"https://github.com/smnh/FontLoader\"\n large=\"true\"\n newWindow=\"true\"\n color=\"light\"\n bevel=\"true\"\n center=\"true\"\n icon=\"fab fa-github\"\n label=\"GITHUB\"\n/\u003e\n\nBefore I dive into the technical details of the solution, I want to show two event timelines showing the difference between the events fired in the web page when using Typekit's [Web Font Loader](https://github.com/typekit/webfontloader \"Web Font Loader\") that uses timeouts and the events fired when using a method that does not use timeouts, as explained in this post. These event timelines are taken from the chrome developer tools.\n\n\u003cImage\n src=\"/images/font-loading-without-timers/typekit-event-timeline.png\"\n title=\"typekit events timeline (with timeouts)\"\n newWindow=\"true\"\n/\u003e\n\n\u003cImage\n src=\"/images/font-loading-without-timers/fontloader-event-timeline.png\"\n title=\"fontloader event timeline (without timeouts)\"\n newWindow=\"true\"\n/\u003e\n\nSo how does it work?\n\n## Step I: Searching for an event.\n\nThe first thing that comes to mind is to add some kind of âresizeâ event listener to an element containing some text and a default font family (e.g., `font-family: serif;`) applied to it. Then, adding a new font-family with a fallback to the default font-family (e.g., `font-family: CustomFont, serif;`) should change the size of the element and fire the âresizeâ event as soon as the new font is loaded and rendered, assuming of course that the new font has different [font metrics](https://en.wikipedia.org/wiki/Typeface#Font_metrics \"Wikipedia - Font metrics\").\n\n```html\n\u003cdiv id=\"tester\" style=\"position:absolute; font-family:serif;\"\u003eHello World!\u003c/div\u003e\n\u003cscript\u003e\n var element = document.getElementById(\"tester\");\n \n element.addEventListener(\"resize\", function() {\n console.log(\"resize fired, font loaded\");\n }, false);\n \n element.style.fontFamily = \"Georgia, serif\";\n\u003c/script\u003e\n```\n\nThe good news is that the DOM specification does have the âresizeâ event. But the [bad news](https://developer.mozilla.org/en-US/docs/Web/API/window.onresize \"MDN - Window.onresize\") is that this event is never fired on HTML elements. It is fired only on the `window` object when its size is changed. So running the above example in the browser wonât log the âfont loadedâ text. The fun part is that IE9 (and maybe other versions of IE) does fire this event when element size changes. But it does so only when binding the event using the non-standard `attachEvent` method with the âonâ prefix and only when adding the new font-family after 0 timeout. Go figure:\n\n```html\n\u003cdiv id=\"tester\" style=\"position:absolute; font-family:serif;\"\u003eHello World!\u003c/div\u003e\n\u003cscript\u003e\n var element = document.getElementById(\"tester\");\n if (typeof element.attachEvent === \"function\") {\n element.attachEvent(\"onresize\", function () {\n console.log(\"onresize fired, font loaded\");\n });\n }
1\n window.setTimeout(function() {\n console.log(\"add new font-family\");\n element.style.fontFamily = \"Georgia, serif\";\n }, 0);\n\u003c/script\u003e\n```\n\nconsole output:\n\n```plain\nadd new font-family \nonresize fired, font loaded\n```\n\nIn any case, the \"resize\" event can't help us. So we need to find another DOM event and utilize it to mimic the âresizeâ event behavior. Great! But what is this event?\n\nLooking at the event handlers available for DOM elements at first glance may seem that there are no events that could help us solve our problem. However, there is one event that can help us, and it is the [scroll](https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#event-type-scroll \"W3C - DOM3 Events - scroll event\") event. According to W3C specification:\n\n\u003e âA user agent **must** dispatch this event when a document view or an element has been scrolled.â\n\nGenerally, we have two explicit ways to scroll an element and trigger the âscrollâ event. The first way is by using user inputs like mouse or keyboard (e.g., dragging scrollbars, scrolling the mouse wheel, pressing up and down keys, etc.). And the second way is by changing the `scrollTop` property of an element using JavaScript. But there is also a third - *implicit* - way to scroll an element, and it is by changing the size of the element or the size of its contents while satisfying a *special initial condition*. Thus, causing the âscrollâ event to be dispatched when the element's size changes.\n\n## Step II: Setting up the environment\n\nBefore playing with events, letâs set up the test environment for our experiments. First, we will create an element called âwrapperâ having a height of 100 pixels. Then, we will put another element called âcontentâ having a high of 150 pixels into the \"wrapper\" element. Right now, we are not using any text or fonts. Our goal is to cause the âscrollâ event to be dispatched on the âwrapperâ element when the size of the âcontentâ element is changed. We will apply `overflow: hidden;` on the âwrapperâ element to clip its contents and hide the scrollbars to disable scrolling it by user inputs while allowing to scroll it using JavaScript. Then we will change the height of the âcontentâ element using JavaScript.\n\n```html\n\u003cdiv id=\"wrapper\" style=\"width:100px; height:100px; overflow:hidden;\"\u003e\n \u003cdiv id=\"content\" style=\"height:150px;\"\u003e\u003c/div\u003e\n\u003c/div\u003e\n\u003cscript type=\"text/javascript\"\u003e\n var wrapper = document.getElementById(\"wrapper\"),\n content = document.getElementById(\"content\");\n \n console.log(\"initial scrollTop: \" + wrapper.scrollTop);\n \n content.style.height = \"140px\";\n console.log(\"scrollTop after decreasing content height: \" + wrapper.scrollTop);\n \n content.style.height = \"160px\";\n console.log(\"scrollTop after increasing content height: \" + wrapper.scrollTop);\n\u003c/script\u003e\n```\n\nRunning the above code will output the following lines in console:\n\n```plain\ninitial scrollTop: 0\nscrollTop after decreasing content height: 0\nscrollTop after increasing content height: 0\n```\n\nThis example shows that simply changing the height of the âcontentâ element will not change the `scrollTop` property of the âwrapperâ element. It was expected because changing the height of the \"scrollable\" element expands its bottom edge downwards or contracts it upwards while its top edge remains at the same place. But it is the scroll position of the element's top edge reflected by the `scrollTop` property. And if the `scrollTop` property was not changed, the âscrollâ event could not have fired.\n\n\n## Step III: Triggering the scroll event by decreasing content size\n\nOk, so we did not succeed with our first try. But no worries, we are engineers, and engineers arenât giving up that easy! This time, letâs add the *special initial condition* that I was talking about. Before setting a new height on the âcontentâ element, we will scroll the âwrapperâ element to the bottom. We will set a new value for the scrollTop property to achieve this. The new `scrollTop` value can be calculated by subtracting `clientHeight` from `scrollHeight`: 150 â 100 = 50. Then we will set a new height on the âcontentâ element and see what happens.\n\n```javascript\nwrapper.scrollTop = wrapper.scrollHeight - wrapper.clientHeight;\nconsole.log(\"initial scrollTop: \" + wrapper.scrollTop);\n \ncontent.style.height = \"140px\";\nconsole.log(\"scrollTop after decreasing content height: \" + wrapper.scrollTop);\n```\n\nconsole output:\n\n```plain\ninitial scrollTop: 50\nscrollTop after decreasing content height: 40\n```\n\nWow! Did you see that? The `scrollTop` property has been changed. But not only that, if we had added the âscrollâ event listener to the âwrapperâ element before changing the contentâs height, the event would have fired right after setting the new height:\n\n```javascript\nwrapper.scrollTop = wrapper.scrollHeight - wrapper.clientHeight;\nconsole.log(\"initial scrollTop: \" + wrapper.scrollTop);
1\n\nwrapper.addEventListener(\"scroll\", function() {\n console.log(\"wrapper scrolled, scrollTop: \" + this.scrollTop);\n}, false);\n\ncontent.style.height = \"140px\";\nconsole.log(\"scrollTop after decreasing content height: \" + wrapper.scrollTop);\n```\n\nconsole output:\n\n```plain\ninitial scrollTop: 50\nscrollTop after decreasing content height: 40\nwrapper scrolled, scrollTop: 40\n```\n\nThis is Magic!\n\nWhat happens is that decreasing the \"content\" element's height while it is scrolled to the bottom pulls the content down to keep its bottom edge glued to the bottom of the \"wrapper\", forcing its `scrollTop` property to change and file the âscrollâ event.\n\n\u003cAlert level=\"warn\"\u003e**\u003cIcon name=\"fas fa-info-circle\"/\u003e Note:** This âmagicâ doesnât work with IE, at least not with IE9.\u003c/Alert\u003e\n\nTo explain this visually, I have prepared a live demo. The demo has a 3D view of the scrollable âwrapperâ element containing the âcontentâ element, shown underneath. To indicate when the âwrapperâ receives âscrollâ events, I have added an event listener for the âscrollâ event that flashes the âscrolledâ label for half a second. For the demo purpose, the wrapperâs `overflow: hidden;` was changed to `overflow: auto;` to enable scrolling using GUI. In addition, two buttons have been added besides the bottom left corner of the âcontentâ element to increase and decrease its height by ±40 pixels. This demo shows that increasing or decreasing content height while it is scrolled to the top just changes its height but doesnât change the `scrollTop` property nor fire the âscrollâ event. But decreasing content height after scrolling it to the bottom will change the `scrollTop` property and fire the scroll event.\n\n\u003ciframe width=\"100%\" height=\"450px\" src=\"//jsfiddle.net/smnh/we6Sp/embedded/result/\" allowfullscreen=\"allowfullscreen\" allowpaymentrequest frameborder=\"0\" scrolling=\"no\"\u003e\u003c/iframe\u003e\n\nHow cool is that? Yet, this is only half of the way. If you have played with the demo, you have probably noticed that the âscrollâ event is fired only when the content's height is decreased. When its height is increased, the content expands downward without changing the `scrollTop` property. In other words, the âscrollâ event isnât fired when content height is increased. However, to load fonts properly, we need the \"scroll\" event to be fired when content is increased or decreased. This is because the loading font can be smaller or greater than the default \"serif\" font.\n\n\n## Step IV: Triggering the scroll event by increasing wrapper size\n\nTo make the âscrollâ event to be fired when the content's height is increased, we need to apply the reverse logic. Instead of changing the height of the âcontentâ element, we need to change the height of the âwrapperâ element while the height of the content element remains the same. This way, when the height of the âwrapperâ element is increased, its content will be pulled down, forcing its `scrollTop` property to change and fire the âscrollâ event.\n\nThis logic can be implemented by adding an absolutely positioned âinnerWrapperâ element inside the âcontentâ element and telling it to âtrackâ the content size by specifying 100% for its width and height properties. Then, adding another content element inside the âinnerWrapperâ element having a greater constant height will lead to the desired result. When the height of the outer âcontentâ element increases, the height of the âinnerWrapperâ will also increase. However, the height of the inner content will remain the same because it is constant.\n\n```html\n\u003cdiv id=\"content\" style=\"position:relative; width:100px; height:150px;\"\u003e\n \u003cdiv id=\"innerWrapper\" style=\"position:absolute; width:100%; height:100%; overflow:hidden;\"\u003e\n \u003cdiv style=\"height: 200px;\"\u003e\u003c/div\u003e\n \u003c/div\u003e\n\u003c/div\u003e\n\u003cscript type=\"text/javascript\"\u003e\n var innerWrapper = document.getElementById(\"innerWrapper\"),\n content = document.getElementById(\"content\");\n\n innerWrapper.scrollTop = innerWrapper.scrollHeight - innerWrapper.clientHeight;\n console.log(\"initial scrollTop: \" + innerWrapper.scrollTop);
1\n\n innerWrapper.addEventListener(\"scroll\", function() {\n console.log(\"innerWrapper scrolled, scrollTop: \" + this.scrollTop);\n }, false);\n\n content.style.height = \"160px\";\n console.log(\"scrollTop after increasing content height: \" + innerWrapper.scrollTop);\n\u003c/script\u003e\n```\n\nconsole output:\n\n```plain\ninitial scrollTop: 50\nscrollTop after increasing content height: 40\ninnerWrapper scrolled, scrollTop: 40\n```\n\ndemo:\n\n\u003ciframe width=\"100%\" height=\"560\" src=\"//jsfiddle.net/yYPeb/13/embedded/result/\" allowfullscreen=\"allowfullscreen\" allowpaymentrequest frameborder=\"0\" scrolling=\"no\"\u003e\u003c/iframe\u003e\n\n\n## Step V: The final result\n\nBy merging both methods, we can now create the final HTML structure and JavaScript code to watch for size changes in both directions and dimensions by listening to âscrollâ events. Unlike previous examples, the following example calculates and applies all the initial dimensions dynamically based on the dimension of an element with some predefined text and a default âserifâ font family. In addition, the difference of ±50 pixels used in previous examples was replaced by ±1 pixel as this difference is enough to catch any size change larger than 1 pixel. And finally, instead of changing content size manually the code adds a new font family to the watched element.\n\n\u003cGist id=\"smnh/10325009\" /\u003e\n\nconsole output on Chrome:\n\n```plain\noriginal content size: 213x18\nsetting new font family\ninnerWrapper scrolled, content size increased: 248x22\n```\n\n\u003cAlert level=\"warn\"\u003e**\u003cIcon name=\"fas fa-info-circle\"/\u003e Update:** Recently Adobe introduced the [Adobe Blank](https://blogs.adobe.com/typblography/2013/03/introducing-adobe-blank.html \"blogs.adobe.com - Introducing Adobe Blank\") font. Using this font as a fallback font removes the need to watch for the decrease of an element's size because the size of an element with text having this font is 0.\u003c/Alert\u003e\n\nNotes:\n\n- The âscrollâ event handler may be called twice for each wrapper if one size dimension is decreased and another is increased. Therefore new element size should be compared with its initial size to filter out secondary âscrollâ events.\n- On some browsers, adding event listeners right after setting `scrollTop` or `scrollLeft` properties will trigger the âscrollâ event immediately. So just like with the previous case, comparing new and initial element sizes will solve this issue.\n- As usual, this method doesnât work with IE, at least not with IE9. Therefore for IE, I recommend using the standard method for watching element size â sampling element size with intervals.\n\nAs a convenient way to detect loaded fonts I have created âFontLoaderâ JavaScript class which is available for download on Github:\n\n\u003cButton\n href=\"https://github.com/smnh/FontLoader\"\n large=\"true\"\n newWindow=\"true\"\n color=\"light\"\n bevel=\"true\"\n center=\"true\"\n icon=\"fab fa-github\"\n label=\"GITHUB\"\n/\u003e\n\nRelated resources:\n\n- [More reliable font events](https://blog.typekit.com/2013/02/05/more-reliable-font-events/ \"More reliable font events\")\n- [Fighting the @font-face FOUT](https://www.paulirish.com/2009/fighting-the-font-face-fout/ \"Paul Irish - Fighting the @font-face FOUT\")\n","urlPath":"/web-font-loading-detection-without-timers"},{"__metadata":{"id":"content/pages/posts/2013-01-20-customizing-appearance-of-uisegmentedcontrol.md","source":"sourcebit-source-filesystem","sourceName":"pages","sourcePath":"/opt/build/repo/content/pages","relSourcePath":"posts/2013-01-20-customizing-appearance-of-uisegmentedcontrol.md","relProjectPath":"content/pages/posts/2013-01-20-customizing-appearance-of-uisegmentedcontrol.md"},"layout":"post","title":"Customizing the appearance of UISegmentedControl","slug":"customizing-appearance-of-uisegmentedcontrol","date":"2013-01-20","tags":["UISegmentedControl","ios"],"description":"How to customize the appearance of UISegmentedControl","excerpt":"The [UISegmentedControl Class](https://developer.apple.com/library/ios/#documentation/uikit/reference/UISegmentedControl_Class/Reference/UISegmentedControl.html) is a UI control available in the [UIKit framework](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIKit_Framework/_index.html).\n\n\u003cImage src=\"/resized-images/segmented-control/generic-segmented-control-sharp-251x49.webp\" alt=\"Generic segmented control\" base64=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAADCAIAAAAV0xgDAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAnklEQVQImQGTAGz/APP2+666yLS/zbC9ybzEz8TL1MTL1MPJ08TL1MXL1cTL1MPJ08XL1cTL1MDI0f3//wCImrAACzkfR2oABDUTPV9nepNheJFGX3o6V3NqgJZXb4gtTWk8V3VqfZQ1U3DCy9YA4Ofueo6mhZWtfpGpoK+/tsPRs7/Osb3LsL3Js7/Os7/OrrrIsb3LtsPQrbrJ9vr+wxNcPK2zEzIAAAAASUVORK5CYII=\" width=\"251\" height=\"49\" hasLargerVersion=\"false\" /\u003e\n\nThe appearance of this control can be easily customized. However, customizing this control is not always apparent to new iOS developers. I was recently asked to create a row of separate buttons that would behave similarly to `UISegmentedControl`. That is, only one button could be selected at any given time. The design looked something like this (specifically this one I took from [dribbble](https://dribbble.com/shots/588694-Blue-Milk-UI-Kit-Updated))\n\n\u003cImage src=\"/resized-images/segmented-control/custom-segmented-control-sharp-294x48.webp\" alt=\"Custom segmented control\" base64=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAACCAIAAADej8umAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAbUlEQVQImQFiAJ3/AGB4gAAOHwAOH1x3fv/++tTS0c7Myu3q6Ofn5uTf383IyO/r6u3q6d3a1+Pf3d7d3QBggYcAUVsAUVthgojg2NeXlZWTkI7MyMfg3t65trOTkI7S0c/i3t6npaOzsK3d3dz6IEEspB2XqgAAAABJRU5ErkJggg==\" width=\"294\" height=\"48\" hasLargerVersion=\"false\" /\u003e\n","markdown_content":"\nYou are probably familiar with the `UISegmentedControl` and its default look which is found in many native iOS apps:\n\n\u003cImage\n src=\"/images/segmented-control/generic-segmented-control.png\"\n title=\"Generic segmented control\"\n newWindow=\"true\"\n/\u003e\n\nThis UI control can be created using the [UISegmentedControl Class][UISegmentedControl], available in the [UIKit framework][UIKit]. The appearance of this control can be easily customized, yet how to do that is not always apparent to new iOS developers. I was recently asked to create a row of separate buttons that would behave similarly to `UISegmentedControl`. That is, only one button could be selected at any given time. The design looked something like this (specifically this one I took from [dribbble][dribbble]):\n\n\u003cImage\n src=\"/images/segmented-control/custom-segmented-control.png\"\n title=\"Custom segmented control\"\n newWindow=\"true\"\n/\u003e\n\nGenerally speaking, the appearance of the `UISegmentedControl` could be customized by using the following methods that set the background and divi
1der images:\n\n```objectivec\n- (void)setBackgroundImage:(UIImage *)backgroundImage\n forState:(UIControlState)state\n barMetrics:(UIBarMetrics)barMetrics;\n\n- (void)setDividerImage:(UIImage *)dividerImage\n forLeftSegmentState:(UIControlState)leftState\n rightSegmentState:(UIControlState)rightState\n barMetrics:(UIBarMetrics)barMetrics;\n```\n\nThe background image fills the stretchable parts of the control segments and their edges. Therefore, this image should have equal-sized left and right parts used for the left and right edges of the control, respectively. These two parts must be separated by a 1-pixel wide line (2 pixels for retina images). This line is used to fill the control segments by stretching itself horizontally. The divider images are used to fill the space between the control segments. These images are used âas isâ without being stretched, and therefore their width defines the space between the control segments. The following diagram shows how these images are used in `UISegmentedControl`:\n\n\u003cImage\n src=\"/images/segmented-control/UISegmentedControl-diagram-1.png\"\n title=\"UISegmentedControl diagram 1\"\n newWindow=\"true\"\n/\u003e\n\nAfter we know how `UISegmentedControl` uses these images to present itself, we can write some code to customize its appearance. In the following example, I have created a subclass of `UISegmentedControl` called `MySegmentedControl,` which has all the needed code to customize the appearance of our control. By the way, subclassing is not required, and everything that is shown here could be done by sending those same messages (an Appleâs terminology for âmethod invocationâ) to an instance of the `UISegmentedControl` class.\n\nFirst, we need to create all five images: two background images for the selected and normal (unselected) states and three divider images. The three divider images are used for:\n\n1. The left and right edges of the adjacent unselected segments\n2. The left edge of the unselected segment and the right edge of the adjacent selected segment\n3. The left edge of the selected segment and the right edge of the adjacent unselected segment\n\n\u003cul style=\"padding-left: 0;\"\u003e\n \u003cli class=\"flex mb-2 items-center\"\u003e\u003cspan class=\"mr-2.5 p-0.5 shadow-sm border border-theme-99\" style=\"line-height: 0\"\u003e\u003cimg src=\"/images/segmented-control/mySegCtrl-selected-bkgd.png\" alt=\"segmented-control-selected\"/\u003e\u003c/span\u003e\u003cspan\u003emySegCtrl-selected-bkgd.png\u003c/span\u003e\u003c/li\u003e\n \u003cli class=\"flex mb-2 items-center\"\u003e\u003cspan class=\"mr-2.5 p-0.5 shadow-sm border border-theme-99\" style=\"line-height: 0\"\u003e\u003cimg src=\"/images/segmented-control/mySegCtrl-normal-bkgd.png\" alt=\"segmented-control-normal\"/\u003e\u003c/span\u003e\u003cspan\u003emySegCtrl-normal-bkgd.png\u003c/span\u003e\u003c/li\u003e\n \u003cli class=\"flex mb-2 items-center\"\u003e\u003cspan class=\"mr-2.5 p-0.5 shadow-sm border border-theme-99\" style=\"line-height: 0\"\u003e\u003cimg src=\"/images/segmented-control/mySegCtrl-divider-none-selected.png\" alt=\"segmented-control-divider-none-selected\"/\u003e\u003c/span\u003e\u003cspan\u003emySegCtrl-divider-none-selected.png\u003c/span\u003e\u003c/li\u003e\n \u003cli class=\"flex mb-2 items-center\"\u003e\u003cspan class=\"mr-2.5 p-0.5 shadow-sm border border-theme-99\" style=\"line-height: 0\"\u003e\u003cimg src=\"/images/segmented-control/mySegCtrl-divider-right-selected.png\" alt=\"segmented-control-divider-right-selected\"/\u003e\u003c/span\u003e\u003cspan\u003emySegCtrl-divider-right-selected.png\u003c/span\u003e\u003c/li\u003e\n \u003cli class=\"flex mb-2 items-center\"\u003e\u003cspan class=\"mr-2.5 p-0.5 shadow-sm border border-theme-99\" style=\"line-height: 0\"\u003e\u003cimg src=\"/images/segmented-control/mySegCtrl-divider-left-selected.png\" alt=\"segmented-control-divider-left-selected\"/\u003e\u003c/span\u003e\u003cspan\u003emySegCtrl-divider-left-selected.png\u003c/span\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\nThen we need to create a subclass of `UISegmentedControl`, by creating a header and an implementation file:\n\n```objectivec\n#import \u003cUIKit/UIKit.h\u003e\n\n@interface MySegmentedControl : UISegmentedControl\n@end\n```\n\nIn the implementation file for `MySegmentedControl` we will override the [designated initializer][designatedInitializer] `- (id)initWithItems:(NSArray *)items` and call the appearance methods:\n\n```objectivec\n#import \"MySegmentedControl.h\"\n\n@implementation MySegmentedControl\n\n- (id)initWithItems:(NSArray *)items {\n self = [super initWithItems:items];\n if (self) {\n // Initialization code\n\n // Set divider images\n [self setDivi
1derImage:[UIImage imageNamed:@\"mySegCtrl-divider-none-selected.png\"]\n forLeftSegmentState:UIControlStateNormal\n rightSegmentState:UIControlStateNormal\n barMetrics:UIBarMetricsDefault];\n [self setDividerImage:[UIImage imageNamed:@\"mySegCtrl-divider-left-selected.png\"]\n forLeftSegmentState:UIControlStateSelected\n rightSegmentState:UIControlStateNormal\n barMetrics:UIBarMetricsDefault];\n [self setDividerImage:[UIImage imageNamed:@\"mySegCtrl-divider-right-selected.png\"]\n forLeftSegmentState:UIControlStateNormal\n rightSegmentState:UIControlStateSelected\n barMetrics:UIBarMetricsDefault];\n\n // Set background images\n UIImage *normalBackgroundImage = [UIImage imageNamed:@\"mySegCtrl-normal-bkgd.png\"];\n [self setBackgroundImage:normalBackgroundImage\n forState:UIControlStateNormal\n barMetrics:UIBarMetricsDefault];\n UIImage *selectedBackgroundImage = [UIImage imageNamed:@\"mySegCtrl-selected-bkgd.png\"];\n [self setBackgroundImage:selectedBackgroundImage\n forState:UIControlStateSelected\n barMetrics:UIBarMetricsDefault];\n }\n return self;\n}\n\n@end\n```\n\nWe are not finished just yet â if we embed the code above in a `UIViewController` we will see that the text inside the controlâs segments is not aligned properly. The following code will produce undesired results:\n\n```objectivec\nNSArray *items = @[@\"first\", @\"second\", @\"third\"];\nMySegmentedControl *mySegmentedControl = [[MySegmentedControl alloc] initWithItems:items];\nmySegmentedControl.frame = CGRectMake(10, 7, self.view.frame.size.width - 20, mySegmentedControl.frame.size.height);\nmySegmentedControl.selectedSegmentIndex = 0;\n[self.view addSubview:mySegmentedControl];\n```\n\nAnd the result:\n\n\u003cImage\n src=\"/images/segmented-control/segmented-control-unaligned-text.png\"\n title=\"Segmented control unaligned text\"\n newWindow=\"true\"\n/\u003e\n\nSo why is this happening? Well, the answer is hidden in the implementation of `UISegmentedControl`. What happens is that `UISegmentedControl` center-aligns text inside its segments according to their width, but the segmentâs width doesnât include the divider images. Hereâs a diagram to clarify things:\n\n\u003cImage\n src=\"/images/segmented-control/UISegmentedControl-diagram-2.png\"\n title=\"UISegmentedControl diagram 2\"\n newWindow=\"true\"\n/\u003e\n\nTo solve this issue we need to adjust the content position of the first and last segments by 1/2 the width of the divider images, assuming that all divider images have the same width. To do this we need to add two lines of code to `MySegmentedControl`:\n\n```objectivec\n[self setContentPositionAdjustment:UIOffsetMake(dividerImageWidth / 2, 0)\n forSegmentType:UISegmentedControlSegmentLeft\n barMetrics:UIBarMetricsDefault];\n[self setContentPositionAdjustment:UIOffsetMake(- dividerImageWidth / 2, 0)\n forSegmentType:UISegmentedControlSegmentRight\n barMetrics:UIBarMetricsDefault];\n```\n\nAnd with that we reach our final result:\n\n\u003cImage\n src=\"/images/segmented-control/segmented-control-aligned-text.png\"\n title=\"Segmented control aligned text\"\n newWindow=\"true\"\n/\u003e\n\n[UISegmentedControl]: https://developer.apple.com/library/ios/#documentation/uikit/reference/UISegmentedControl_Class/Reference/UISegmentedControl.html \"UISegmentedControl Class Reference\"\n[UIKit]: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIKit_Framework/_index.html \"UIKit Framework Reference\"\n[dribbble]: https://dribbble.com/shots/588694-Blue-Milk-UI-Kit-Updated \"Blue Milk UI Kit (Updated)\"\n[designatedInitializer]: https://developer.apple.com/library/ios/documentation/general/conceptual/CocoaEncyclopedia/Initialization/Initialization.html#//apple_ref/doc/uid/TP40010810-CH6-SW3 \"iOS Developer Library - Multiple Initializers and the Designated Initializer\"\n","urlPath":"/customizing-appearance-of-uisegmentedcontrol"},{"__metadata":{"id":"content/pages/posts/2012-08-06-javascript-profiler.md","source":"sourcebit-source-filesystem","sourceName":"pages","sourcePath":"/opt/build/repo/content/pages","relSourcePath":"posts/2012-08-06-javascript-profiler.md","relProjectPath":"content/pages/posts/2012-08-06-javascript-profiler.md"}
1,"layout":"post","title":"JavaScript Profiler","slug":"javascript-profiler","date":"2012-08-06","tags":["javascript","profile","performance"],"description":"Measuring and profiling JavaScript execution time","excerpt":"A custom method for profiling JavaScript execution times and getting the results as an object, showing them on the screen, or printing to console.\n\n\u003cImage src=\"/resized-images/js-profiler/javascript-profiler-html-table-sharp-642x261.webp\" alt=\"JavaScript Profiler HTML Result Table\" base64=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAGCAIAAABFHomwAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA10lEQVQYlTXOIY6GMBCG4d4Cyy04AYdA4BD1CJIafAUCSfLLiooVCOQIREXFCMQYBAKBQCAqaiq62fLvYyfvl2GMMc75MAzjOE7TtCyLlFII0SVVVXVd1yec8zzPWVEURAQAx3F472OMT4KIRHTfdwjhSGKMQghW1zURaa2ttWey7zsirutqjHHOvTEieu/btv0LrutSSgGAMeYNrLUmeZ7HOWeTEMI3iDFaawFg3/cY43meRLRt27v6XhHx+1KWZX3fN00jpdRaz/OslPr8+0mmBADKsvwFx/fJZ24XIlkAAAAASUVORK5CYII=\" width=\"642\" height=\"261\" hasLargerVersion=\"false\" /\u003e\n","markdown_content":"\nEvery web developer who developed web applications or sites for mobile devices knows that there is no easy way to profile JavaScript execution times. Some developer tools are available for mobile debugging, such as [iWebInspector][iwebinspector] and [weinre][weinre] (WEb INspector REmote). However, none of them can profile JavaScript execution times on remote devices as it can be done in Developer Tools in Chrome and Safari or Firebug in Firefox.\n\nThe following example shows the profile results in Chrome Developer Tools and Firebug of some simple code.\n\n```js\nfunction MyClass() {\n\tthis.foo();\n}\n\nMyClass.prototype.foo = function foo() {\n\tvar i;\n\tfor (i = 0; i \u003c 100; i++) {\n\t\tdocument.createElement(\"div\");\n\t}\n\tthis.bar();\n};\n\nMyClass.prototype.bar = function bar() {\n\tvar i;\n\tfor (i = 0; i \u003c 100; i++) {\n\t\tdocument.createElement(\"div\");\n\t}\n};\n\nvar obj = {\n\tfuncA: function funcA() {\n\t\tvar i;\n\t\tfor (i = 0; i \u003c 10; i++) {\n\t\t\tthis.funcB();\n\t\t}\n\t},\n\tfuncB: function funcB() {\n\t\tvar i, c;\n\t\tfor (i = 0; i \u003c 10; i++) {\n\t\t\tc = new MyClass();\n\t\t\tc.bar();\n\t\t}\n\t}\n};\n\nfunction doSomething() {\n\tvar i;\n\tfor (i = 0; i \u003c 10; i++) {\n\t\tobj.funcA();\n\t}\n\treturn false;\n}\n```\n\nI have invoked the `doSomething()` function while recording profile in Chrome Developer Tools and Firebug and got the following results:\n\n\u003cImage\n src=\"/images/js-profiler/jsProfilerChrome.png\"\n alt=\"Profiling JavaScript execution time in Chrome\"\n caption=\"Profiling JavaScript execution time in Chrome gives an in-depth overview of function call hierarchy with an average execution time of code within each function (the self âcolumnâ) and an average execution time of all functions called from within that function (the total âcolumnâ).\"\n newWindow=\"true\"\n/\u003e\n\n\u003cImage\n src=\"/images/js-profiler/jsProfileFirebug.png\"\n alt=\"Profiling JavaScript execution time in Firefox\"\n caption=\"Profiling JavaScript execution time in Firebug gives an overview of executed functions. For each function the overview shows number of calls, own and total times, average time and other data.\"\n newWindow=\"true\"\n/\u003e\n\nOne thing to note regarding the Chrome profiling tool is that it doesn't show the total count of each executed function, as shown in Firebug. In addition, the times shown in this tool aren't accurate. The \"self\" time of the \"MyClass\" constructor is too high relative to the time of functions \"foo\" and \"bar\" invoked from within that constructor. At the same time, there is no code executed inside this constructor except the call for the \"foo\" function.\n\nUnlike the Chrome profiling tool, Firebug doesn't show function call hierarchy. On the other hand, Firebug does show other valuable data.\n\nIn any case, none of the above tools can help us when we want to profile JavaScript on mobile devices. We have no Chrome Developer Tools or Firebug on mobile devices. [Weinre][weinre], a tool I've mentioned before, at first glance, seems to allow profiling JavaScript on mobile devices remotely, just like Webkit's Web Inspector. But it doesn't have the \"Profiles\" tab in its panel, so it is no go. And [iWebInspector][iwebinspector] allows profiling JavaScript executed from iOS simulator only.\n\nSo how are we going to fix that?\n\nThe idea is to create a JavaScript library that allows simple profiling of functions and class methods by collecting their execution times. Then, parsing collected data and displaying it in a remote console or even in a nice UI dialog. One downside of this tool is that all functions and classes intended for profiling must be registered with the profiler before profiling them.\n\n## Step 1: Registering functions and classes to be profiled\n\nAs I have already noted, all functions and classes intended for profiling must be registered with the profiler before starting the profiler. Before registering any functions we need to instantiate profiler object: `var profiler = new JsProfiler();`. Then we can start registering functions, classes, and objects on that profiler instance.\n\n### Registering functions:\n\nTo register single functions, invoke the following method of the profiler instance:\n\n```javascript\nregisterFunction(contextObject, functionName, contextObjectLabel);\n```\n\nThe `contextObject` parameter is the object on which the registered function is defined. The `functionName` parameter is the string indicating the name of the registered function. In other words, the reference of the registered function can be obtained by using `contextObject[functionName]`. This string will also be used to identify the function in profiler results. If your function is defined in the global scope, pass the `window` as the `contextObject`. This makes sense because the reference to all global functions can be obtained using `window[functionName]`. The third parameter is the `contetObjectLabel`, a string used to prefix function name in profiler results.\n\n### Registering objects:\n\nTo register all functions defined on an object, including any [getters][getters] and [setters][setters], invoke the following method of the profiler instance:\n\n```javascript\nregisterObject(object, objectLabel)\n```\n\nThe `object` parameter is the object whose functions you want to register. And the `objectLabel` parameter is the string that will be used to prefix functions of this object in profiler results.\n\n### Registering classes:\n\nTo register classes, including their constructor function, methods defined on the prototype (instance), and the class (static), as well as any [getters][getters] and [setters][setters], invoke the following method of the profiler instance:\n\n```javascript\nregisterClass(contextObject, className)\n```\n\nAnd just like registering functions, the first parameter is the object on which the registered class is defined. And the second parameter is the string indicating the name of the class. This string will be used to prefix class static functions and class instance methods in profiler results. Note that only functions from the first prototype in the [prototype chain][prototypeChain] of the class will be registered.\n\n### All together\n\nContinuing the first example, we can now use the following code to register the global scoped function `doSomething()`, the functions declared on object `obj`, and the class `MyClass()` including its constructor and instance methods:\n\n```javascript\nvar profiler = new JsProfiler();\nprofiler.registerFunction(window, \"doSomething\", \"window\");\nprofiler.registerClass(window, \"MyClass\");\nprofiler.registerObject(obj, \"obj\");\n```\n\n## Step 2: Profiling javascript execution time\n\nAfter registering all functions that we want to profile, we need to start the profiler to begin profiling the code and collecting the profile data. Invoking registered functions won't collect any profile data without starting the profiler. To start the profiler, we need to invoke its `start` method:\n\n```javascript\nprofiler.start();
1\n```\n\nNow we can start invoking registered functions and collect their execution times. Every time one of the registered functions is invoked, the profiler will record its execution time.\n\nLet's execute the `doSomething()` function, which will call all other registered functions:\n\n```javascript\ndoSomething();\n```\n\nAfter our code has finished executing and collecting the profile data, we can stop the profiler. You guessed right! Stopping profiler instance is even simpler than starting it:\n\n\n```javascript\nprofiler.stop();\n```\n\n## Step 3: Printing results\n\nOnce we have collected the profile data, we can decide how to present it. But first, we need to get the raw collected data by running:\n\n```javascript\nvar results = profiler.getResults();\n```\n\nThe return value of the `getResults` method is an object representing the collected profile data. This object has a hierarchical tree structure matching the hierarchical invocation of registered functions. Every object in the tree represents an invoked function and contains the collected profile data of that function. The objects in the first level represent the functions invoked first in each call stack. And the objects nested inside the `children` property represent functions invoked by \"parent\" functions. \n\nFor example, assuming we have three registered functions defined in the global scope: `rootFunction`, `innerFunction1`, and `innerFunction2`, where `rootFunction` calls the two other functions. We would get the following result:\n\n```json\n{\n\t\"window.rootFunction()\": {\n\t\t\"count\": 1,\n\t\t\"total\": 250,\n\t\t\"totalAverage\": 250,\n\t\t\"totalChildrenTime\": 200,\n\t\t\"self\": 50,\n\t\t\"selfAverage\": 50,\n\t\t\"children\": {\n\t\t\t\"window.innerFunction1()\": {\n\t\t\t\t\"count\": 1,\n\t\t\t\t\"total\": 100,\n\t\t\t\t\"totalAverage\": 100,\n\t\t\t\t\"totalChildrenTime\": 0,\n\t\t\t\t\"self\": 100,\n\t\t\t\t\"selfAverage\": 100,\n\t\t\t\t\"children\": {}\n\t\t\t},\n\t\t\t\"window.innerFunction2()\": {\n\t\t\t\t\"count\": 1,\n\t\t\t\t\"total\": 100,\n\t\t\t\t\"totalAverage\": 100,\n\t\t\t\t\"totalChildrenTime\": 0,\n\t\t\t\t\"self\": 100,\n\t\t\t\t\"selfAverage\": 100,\n\t\t\t\t\"children\": {}\n\t\t\t}\n\t\t}\n\t}\n}\n```\n\nI have created two different output methods to display this data nicely. Of course, you can create other output formats and destinations. Now, after having the results object, we can pass it to one of the output methods:\n\n```javascript\nJsProfiler.HtmlTableGenerator.showTable(results);\n```\n\nThis method will show a dialog with an HTML table that lets us browse through call-stack hierarchy similar to Chrome Profiler. However, unlike Chrome, this table has more information, similar to FireBug:\n\n\u003cImage\n src=\"/images/js-profiler/javascript-profiler-html-table.png\"\n title=\"JavaScript Profiler HTML Result Table\"\n newWindow=\"true\"\n/\u003e\n\nHere is a demo of using the profiler on the code from the original example and the result table from JsFiddle:\n\n\u003ciframe style=\"width: 100%; height: 360px; margin-bottom: 20px; border: none;\" src=\"https://jsfiddle.net/smnh/wu2uS/embedded/result,js\"\u003e\u003c/iframe\u003e\n\nThe second method outputs the data to the browserâs console using the [console.log](https://developer.mozilla.org/en-US/docs/DOM/console.log \"MDN - console.log\") method:\n\n```javascript\nJsProfiler.ConsoleTableGenerator.printResults(results)\n```\n\n\u003cImage\n src=\"/images/js-profiler/javascript-profiler-console-table.png\"\n title=\"JavaScript Profiler Console Table\"\n newWindow=\"true\"\n/\u003e\n\nYou can also use the raw data and send it elsewhere for later examination.\n\nThe JsProfiler is available at [GitHub](https://github.com/smnh/JsProfiler \"JsProfiler GitHub\"), just include the `jsProfiler.js` file in your code and start profiling your co
1de to make the web better!\n\n\u003cButton\n href=\"https://github.com/smnh/JsProfiler\"\n large=\"true\"\n newWindow=\"true\"\n color=\"light\"\n bevel=\"true\"\n center=\"true\"\n icon=\"fab fa-github\"\n label=\"GITHUB\"\n/\u003e\n\n[iwebinspector]: https://www.iwebinspector.com/ \"iWebInspector\"\n[weinre]: https://people.apache.org/~pmuellr/weinre/ \"weinre\"\n[getters]: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/get \"MDN - get\"\n[setters]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/set \"MDN - set\"\n[prototypeChain]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain \"MDN - Inheritance and the prototype chain\"\n","urlPath":"/javascript-profiler"},{"__metadata":{"id":"content/pages/posts/2012-08-02-waiting-for-web-fonts-to-load.md","source":"sourcebit-source-filesystem","sourceName":"pages","sourcePath":"/opt/build/repo/content/pages","relSourcePath":"posts/2012-08-02-waiting-for-web-fonts-to-load.md","relProjectPath":"content/pages/posts/2012-08-02-waiting-for-web-fonts-to-load.md"},"layout":"post","title":"Waiting for web fonts to load","slug":"waiting-for-web-fonts-to-load","date":"2012-08-02","tags":["javascript","webfonts","fonts","loading","fout"],"description":"A javascript function that detects and notifies you when particular fonts have been loaded and are ready to be used on a webpage","excerpt":"A small JavaScript utility function that detects and notifies you when particular fonts have been loaded and are ready to be used on a webpage\n","markdown_content":"\n\u003cAlert\u003e**\u003cIcon name=\"fa-solid fa-circle-info\" class=\"mr-2\"/\u003e**Since writing this post, I have created a better way to identify when web fonts have been loaded and wrote a new [blog post](/web-font-loading-detection-without-timers) about it.\u003c/Alert\u003e\n\nWhile working on a project where I was required to sample dimensions of HTML elements with specific web fonts, I have bumped into a problem. There was no DOM event signaling when particular fonts were loaded and **rendered**. And even though all the fonts were base64 encoded and encoded into the web page. The browser didn't render them immediately. There was a slight delay between the first-page render and fonts rendering.\n\nAfter a short research, I have found a hint in [typekitâs blog](https://blog.typekit.com/2010/10/18/more-control-with-typekits-font-events/ \"More control with Typekitâs font events\"). The solution presented in that post was to create an off-screen element, one for each font, with a bit of text and appropriate font-family value. Then periodically compare that element width to a reference width. Once element width changes, we can say that font is loaded and rendered.\n\nAnd because I could not use 3rd party dependencies, I wrote a short `onFontsLoad` function that implements a similar solution. This function receives an array of strings representing required font families and a callback function that is invoked when all passed fonts are loaded and rendered:\n\n\u003cGist id=\"smnh/1bc5aaead6ce52c39e9e\" /\u003e\n\nThe `onFontsLoad` function also gets a third, optional parameter, which is an object with two fields:\n\n1. `maxNumOfTries` â maximum number tries to retrieve and compare the element dimension.\n2. `tryIntervalMs` â interval in milliseconds between retrieving and comparing the element dimension.\n\nThe `onFontsLoad` function iterates over the passed font-families array. For every font-family, it creates an element with a [pangram](https://en.wikipedia.org/wiki/Pangram \"Pangram\") text, and it applies a style with a matching font-family. It also creates one element with the \"serif\" font and stores its dimensions for reference. The pangram ensures that a change in size will occur even if the dimension of a single character of the requested font is different from the \"serif\" font. The font-size that is used on all the elements is 40px. This ensures that even a slight font appearance changes the element's size. Then, it polls the dimensions of the elements. When the dimensions of all the elements have been changed, the callback is invoked. However, the callback is invoked anyway if some fonts are still not loaded after a specific time (`maxNumOfTries` * `tryIntervalMs`).\n\nThe callback is executed with a single `error` parameter. This parameter would be `null` if all fonts were successfully loaded. Otherwise, this parameter will be an object with a `message` property with an error message and additional `notLoadedFontFamilies` property with an array of font-families that weren't loaded.\n\nHere is a usage example:\n\n```javascript\n// Show some loading indicator while fonts on a page are loaded\n \nonFontsLoad([\"myFont1\", \"myFont2\"], function(error) {\n // Hide loading indicator and show elements with loaded fonts\n});\n```\n","urlPath":"/waiting-for-web-fonts-to-load"}]},"__N_SSG":true},"page":"/","query":{},"buildId":"DUIJVVroaEOa-CVSMF9kL","isFallback":false,"gsp":true,"scriptLoader":[]}</script>
1</body></html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.