[{"_path":"/card-stack","_draft":false,"_partial":false,"_empty":false,"title":"Make your own animated stack of elements.","description":"I show you how to make a stack of Divs that will flip positions.","excerpt":{"type":"root","children":[{"type":"element","tag":"my-nuxt-link","props":{"target":"/example/card-stack-example"},"children":[{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Before we start, check out the finished product here."}]}]},{"type":"element","tag":"h2","props":{"id":"overview"},"children":[{"type":"text","value":"Overview"}]},{"type":"element","tag":"ul","props":{},"children":[{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"First we make the outermost wrapper element and position it where we want on the page."},{"type":"element","tag":"ul","props":{},"children":[{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"This will be position relative"}]}]}]},{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"Then we make at least 3 \"card\" elements."},{"type":"element","tag":"ul","props":{},"children":[{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"these will be position absolute."}]}]}]},{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"Next we will position the cards programatically."}]},{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"Finally we will create an animation that plays while cycling through the cards."}]}]},{"type":"element","tag":"h2","props":{"id":"disclaimer"},"children":[{"type":"text","value":"Disclaimer"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"I built this inside a Vue.js project but everything shown here should work fine in any project as I mostly use vanilla Javascript. Also I am using TailwindCss but that isn't necessary."}]},{"type":"element","tag":"h2","props":{"id":"setting-up-the-dom"},"children":[{"type":"text","value":"Setting up the DOM"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Let's start with a "},{"type":"element","tag":"em","props":{},"children":[{"type":"text","value":"main"}]},{"type":"text","value":" element. This will assist us in positioning the rest of our elements. This should have a minimum height of the screen so we can position child elements easily. We also need to give it overflow hidden, as the animated elements will cause a scroll bar for the x-axis to appear and disapear."}]},{"type":"element","tag":"code","props":{"code":"<main class=\"min-h-screen flex flex-col items-center justify-center overflow-x-hidden\">\n\n</main\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"<main class=\"min-h-screen flex flex-col items-center justify-center overflow-x-hidden\">\n\n</main\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Before we create our card stack, we'll need a button group. Here I'm using Vue's \"@click\" directive, but you can add the click functionality in whatever way you choose, like with a classic event listener. Each button should call the same function but with a different argument, in this case a string that either says \"back\" or \"forward\"."}]},{"type":"element","tag":"code","props":{"code":"<div class=\"flex gap-5\">\n    <button @click=\"flip('back')\">Prev</button>\n    <button @click=\"flip('forward')\">Next</button>\n</div>\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"<div class=\"flex gap-5\">\n    <button @click=\"flip('back')\">Prev</button>\n    <button @click=\"flip('forward')\">Next</button>\n</div>\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Next we'll want to wrap our cards in a div, I've given mine an id of \"my-stack\". The important thing is that this div is position \"relative\", as each of the cards will be position \"absolute\" and their actual position on the page will be in relation to this parent element. I've also given it a minimum height, as all of it's children are \"absolute\" and will leave it with zero height, which will be bothersome to the rest of your page. We'll also be setting a width here which will control the width of the children."}]},{"type":"element","tag":"code","props":{"code":"<div id=\"my-stack\" class=\"relative w-1/3 min-h-[400px]\">\n\n</div>\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"<div id=\"my-stack\" class=\"relative w-1/3 min-h-[400px]\">\n\n</div>\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"This works best with at least 3 cards. You can hard code them in your html or create them dynamically with whatever tools you are using. For this example I am using Vue's \"v-for\" directive to make card elements for each item in an array. Either way we will be creating an array of these elements after the DOM has been rendered, which means you should be ready to use lifecycle hooks if needed. Don't forget to give each of these elements \"absolute\" position."}]},{"type":"element","tag":"code","props":{"code":"<div v-for=\"item in items\" :key=\"item\" \nclass=\"my-card absolute w-full bg-primary \nborder-4 border-white text-center text-2xl md:text-5xl\">\n\n    <h1 class=\"my-12 md:my-32\">{{ item }}</h1>\n\n</div>\n\n<script setup>\nconst items = [\n    'item 1',\n    'item 2',\n    'item 3',\n]\n</script>\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"<div v-for=\"item in items\" :key=\"item\" \nclass=\"my-card absolute w-full bg-primary \nborder-4 border-white text-center text-2xl md:text-5xl\">\n\n    <h1 class=\"my-12 md:my-32\">{{ item }}</h1>\n\n</div>\n\n<script setup>\nconst items = [\n    'item 1',\n    'item 2',\n    'item 3',\n]\n</script>\n"}]}]}]},{"type":"element","tag":"h2","props":{"id":"javascript-functionality"},"children":[{"type":"text","value":"Javascript functionality"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"First we need to initialize an empty array to hold our card elements. This is only really necessary if we add our cards dynamically. Then we are going to make a utility function we will use later. I strait up took this function from stack overflow and shortened it with arrow function syntax. Basically It takes an integer as an argument, which is how long to wait in miliseconds. Then it uses a Promise object so we can use \"await\" when we call it."}]},{"type":"element","tag":"code","props":{"code":"let cards = []\n\nconst sleep = ms => new Promise(r => setTimeout(r, ms))\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"let cards = []\n\nconst sleep = ms => new Promise(r => setTimeout(r, ms))\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Now lets talk about how this will work. We start with an array of card elements. When the animation is triggered, the array is re-ordered. Then we animate either the first or last element away depending on which button was pressed. This animation only moves the element on the x-axis. Then we adjust the positions of each element in the array before animating that one element back in."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Our two main functions are called flip() and rePosition()."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"flip() is what is called when the prev or next buttons are clicked. This function needs to be \"async\" because we will be using await a little later. First we make sure the \"el\" variable is clear. Then we use the classic array methods:"}]},{"type":"element","tag":"ul","props":{},"children":[{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"array.shift() removes the first element in an array and returns it"}]},{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"array.push() adds an element to the end of an array"}]},{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"array.pop() removes the last element in an array and returns it."}]},{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"array.unshift() adds an element to the beginning of an array"}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Depending on which direction (string argument) is given, we will either take the first item and place it at the end of the array, or we will take the last item and place it at the beginning."}]},{"type":"element","tag":"code","props":{"code":"const flip = async (direction) => {\n        \n    let el = null\n\n    if (direction === 'forward') {\n        el = cards.shift()\n        cards.push(el)\n\n    } else {\n        el = cards.pop()\n        cards.unshift(el)\n    }\n}\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"const flip = async (direction) => {\n        \n    let el = null\n\n    if (direction === 'forward') {\n        el = cards.shift()\n        cards.push(el)\n\n    } else {\n        el = cards.pop()\n        cards.unshift(el)\n    }\n}\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Now our array of cards has been changed, but nothing has changed on the page. Next we will animate out an element, finally make use of our sleep() function, run our rePosition() function, then animate the element back in. Here we go."}]},{"type":"element","tag":"code","props":{"code":"const flip = async (direction) => {\n    // previous code\n\n    el.style.transform = 'translateX(150%)'\n    await sleep(500)\n\n    rePosition(cards)\n\n    el.style.transform = 'translateX(0)'\n}\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"const flip = async (direction) => {\n    // previous code\n\n    el.style.transform = 'translateX(150%)'\n    await sleep(500)\n\n    rePosition(cards)\n\n    el.style.transform = 'translateX(0)'\n}\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"What we are doing here is using Javascript to set a CSS property called \"transform\", and there we pass a CSS function called translateX() which will move the element on the x-axis. we pass '150%' to this function which will move it to the right 1.5x the width of itself. We then make flip() wait by preceeding our sleep() function with the \"await\" keyword, and pass 500 ms as an argument. Before animating our element back in we will run the rePosition() function and pass it our re-ordered array of cards."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"We want each card in our stack to be a little down, and a little to the right of the one before it. To do this we will be setting the \"top\" and \"right\" CSS properties, which position the element related to it's \"relative\" parent. We also will be using a CSS property called \"z-index\" which controls the position on the z-axis. Basically which element is on top of which other element."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Our rePosition() function takes our card array as an argument. In it we will loop through each of the elements, calculate their new position values, and then apply them. The \"top\" and \"right\" preperties will be set using a pixel value followed by 'px' (i.e. '50px'). The tricky part is that we are using the index of each array element as a multiplier in order to evenly space them on screen."}]},{"type":"element","tag":"code","props":{"code":"const rePosition = (array) => {\n\n    for (const el of array) {\n        // calculate positions by index\n        const index = array.indexOf(el)\n        const top = index * 10\n        const right = 0 - top\n\n        // apply positions\n        el.style.top = top + 'px'\n        el.style.right = right + 'px'\n        el.style.zIndex = 10 - index\n    }\n}\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"const rePosition = (array) => {\n\n    for (const el of array) {\n        // calculate positions by index\n        const index = array.indexOf(el)\n        const top = index * 10\n        const right = 0 - top\n\n        // apply positions\n        el.style.top = top + 'px'\n        el.style.right = right + 'px'\n        el.style.zIndex = 10 - index\n    }\n}\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Next we need to set a CSS property called \"transition\" which will make our \"translateX\" actually look like an animation. \"transition\" looks for changes on whatever property you give it and fills in the blanks between start and finish positions. This can be done on a stylesheet, inside of a style tag, or even inline on your element."}]},{"type":"element","tag":"code","props":{"code":"<style>\n.my-card {\n    transition: transform .5s;\n}\n</style>\n\nor\n\n<div class=\"my-card\" style=\"transition: transform .5s;\">\n</div>\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"<style>\n.my-card {\n    transition: transform .5s;\n}\n</style>\n\nor\n\n<div class=\"my-card\" style=\"transition: transform .5s;\">\n</div>\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Now finally at the bottom of the script we need to fill our empty cards array, and call rePosition() for the first time, otherwise our cards will be stacked directly on top of each other until the first animation is triggered. Here is where we need to use a onMounted or equivilant lifecycle hook to wrap around these actions, that is if you are using a virtual dom like Vue and React do, otherwise just having these at the bottom of the script should suffice."}]},{"type":"element","tag":"code","props":{"code":"onMounted(() => {\n    cards = Array.from(document.querySelectorAll('.my-card'))\n\n    rePosition(cards)\n})\n\nor\n\ncards = Array.from(document.querySelectorAll('.my-card'))\n\nrePosition(cards)\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"onMounted(() => {\n    cards = Array.from(document.querySelectorAll('.my-card'))\n\n    rePosition(cards)\n})\n\nor\n\ncards = Array.from(document.querySelectorAll('.my-card'))\n\nrePosition(cards)\n"}]}]}]},{"type":"element","tag":"quick-aside","props":{},"children":[{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"I encountered a bug that seems to be an issue with Nuxt 3. When navigating to the example page, my onMounted hook was running before the DOM was rendered. It looks like the most popular fix is to wrap everyting inside your onMounted hook, inside a setTimeout function. 100ms did it for me!"}]}]},{"type":"element","tag":"h2","props":{"id":"conclusion"},"children":[{"type":"text","value":"Conclusion"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Well that was a lot of work, but if everything went right then you have a component that can be used to display any number of cards, however if I were this to showcase several cards, I would only render the top 5 or so. Also feel free to style these any way you see fit!"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Happy coding!"}]}]},"thumbnail":"/images/card-stack.jpg","slug":"card-stack","author":"Tony Green","draft":true,"body":{"type":"root","children":[{"type":"element","tag":"my-nuxt-link","props":{"target":"/example/card-stack-example"},"children":[{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Before we start, check out the finished product here."}]}]},{"type":"element","tag":"h2","props":{"id":"overview"},"children":[{"type":"text","value":"Overview"}]},{"type":"element","tag":"ul","props":{},"children":[{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"First we make the outermost wrapper element and position it where we want on the page."},{"type":"element","tag":"ul","props":{},"children":[{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"This will be position relative"}]}]}]},{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"Then we make at least 3 \"card\" elements."},{"type":"element","tag":"ul","props":{},"children":[{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"these will be position absolute."}]}]}]},{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"Next we will position the cards programatically."}]},{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"Finally we will create an animation that plays while cycling through the cards."}]}]},{"type":"element","tag":"h2","props":{"id":"disclaimer"},"children":[{"type":"text","value":"Disclaimer"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"I built this inside a Vue.js project but everything shown here should work fine in any project as I mostly use vanilla Javascript. Also I am using TailwindCss but that isn't necessary."}]},{"type":"element","tag":"h2","props":{"id":"setting-up-the-dom"},"children":[{"type":"text","value":"Setting up the DOM"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Let's start with a "},{"type":"element","tag":"em","props":{},"children":[{"type":"text","value":"main"}]},{"type":"text","value":" element. This will assist us in positioning the rest of our elements. This should have a minimum height of the screen so we can position child elements easily. We also need to give it overflow hidden, as the animated elements will cause a scroll bar for the x-axis to appear and disapear."}]},{"type":"element","tag":"code","props":{"code":"<main class=\"min-h-screen flex flex-col items-center justify-center overflow-x-hidden\">\n\n</main\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"<main class=\"min-h-screen flex flex-col items-center justify-center overflow-x-hidden\">\n\n</main\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Before we create our card stack, we'll need a button group. Here I'm using Vue's \"@click\" directive, but you can add the click functionality in whatever way you choose, like with a classic event listener. Each button should call the same function but with a different argument, in this case a string that either says \"back\" or \"forward\"."}]},{"type":"element","tag":"code","props":{"code":"<div class=\"flex gap-5\">\n    <button @click=\"flip('back')\">Prev</button>\n    <button @click=\"flip('forward')\">Next</button>\n</div>\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"<div class=\"flex gap-5\">\n    <button @click=\"flip('back')\">Prev</button>\n    <button @click=\"flip('forward')\">Next</button>\n</div>\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Next we'll want to wrap our cards in a div, I've given mine an id of \"my-stack\". The important thing is that this div is position \"relative\", as each of the cards will be position \"absolute\" and their actual position on the page will be in relation to this parent element. I've also given it a minimum height, as all of it's children are \"absolute\" and will leave it with zero height, which will be bothersome to the rest of your page. We'll also be setting a width here which will control the width of the children."}]},{"type":"element","tag":"code","props":{"code":"<div id=\"my-stack\" class=\"relative w-1/3 min-h-[400px]\">\n\n</div>\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"<div id=\"my-stack\" class=\"relative w-1/3 min-h-[400px]\">\n\n</div>\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"This works best with at least 3 cards. You can hard code them in your html or create them dynamically with whatever tools you are using. For this example I am using Vue's \"v-for\" directive to make card elements for each item in an array. Either way we will be creating an array of these elements after the DOM has been rendered, which means you should be ready to use lifecycle hooks if needed. Don't forget to give each of these elements \"absolute\" position."}]},{"type":"element","tag":"code","props":{"code":"<div v-for=\"item in items\" :key=\"item\" \nclass=\"my-card absolute w-full bg-primary \nborder-4 border-white text-center text-2xl md:text-5xl\">\n\n    <h1 class=\"my-12 md:my-32\">{{ item }}</h1>\n\n</div>\n\n<script setup>\nconst items = [\n    'item 1',\n    'item 2',\n    'item 3',\n]\n</script>\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"<div v-for=\"item in items\" :key=\"item\" \nclass=\"my-card absolute w-full bg-primary \nborder-4 border-white text-center text-2xl md:text-5xl\">\n\n    <h1 class=\"my-12 md:my-32\">{{ item }}</h1>\n\n</div>\n\n<script setup>\nconst items = [\n    'item 1',\n    'item 2',\n    'item 3',\n]\n</script>\n"}]}]}]},{"type":"element","tag":"h2","props":{"id":"javascript-functionality"},"children":[{"type":"text","value":"Javascript functionality"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"First we need to initialize an empty array to hold our card elements. This is only really necessary if we add our cards dynamically. Then we are going to make a utility function we will use later. I strait up took this function from stack overflow and shortened it with arrow function syntax. Basically It takes an integer as an argument, which is how long to wait in miliseconds. Then it uses a Promise object so we can use \"await\" when we call it."}]},{"type":"element","tag":"code","props":{"code":"let cards = []\n\nconst sleep = ms => new Promise(r => setTimeout(r, ms))\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"let cards = []\n\nconst sleep = ms => new Promise(r => setTimeout(r, ms))\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Now lets talk about how this will work. We start with an array of card elements. When the animation is triggered, the array is re-ordered. Then we animate either the first or last element away depending on which button was pressed. This animation only moves the element on the x-axis. Then we adjust the positions of each element in the array before animating that one element back in."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Our two main functions are called flip() and rePosition()."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"flip() is what is called when the prev or next buttons are clicked. This function needs to be \"async\" because we will be using await a little later. First we make sure the \"el\" variable is clear. Then we use the classic array methods:"}]},{"type":"element","tag":"ul","props":{},"children":[{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"array.shift() removes the first element in an array and returns it"}]},{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"array.push() adds an element to the end of an array"}]},{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"array.pop() removes the last element in an array and returns it."}]},{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"array.unshift() adds an element to the beginning of an array"}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Depending on which direction (string argument) is given, we will either take the first item and place it at the end of the array, or we will take the last item and place it at the beginning."}]},{"type":"element","tag":"code","props":{"code":"const flip = async (direction) => {\n        \n    let el = null\n\n    if (direction === 'forward') {\n        el = cards.shift()\n        cards.push(el)\n\n    } else {\n        el = cards.pop()\n        cards.unshift(el)\n    }\n}\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"const flip = async (direction) => {\n        \n    let el = null\n\n    if (direction === 'forward') {\n        el = cards.shift()\n        cards.push(el)\n\n    } else {\n        el = cards.pop()\n        cards.unshift(el)\n    }\n}\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Now our array of cards has been changed, but nothing has changed on the page. Next we will animate out an element, finally make use of our sleep() function, run our rePosition() function, then animate the element back in. Here we go."}]},{"type":"element","tag":"code","props":{"code":"const flip = async (direction) => {\n    // previous code\n\n    el.style.transform = 'translateX(150%)'\n    await sleep(500)\n\n    rePosition(cards)\n\n    el.style.transform = 'translateX(0)'\n}\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"const flip = async (direction) => {\n    // previous code\n\n    el.style.transform = 'translateX(150%)'\n    await sleep(500)\n\n    rePosition(cards)\n\n    el.style.transform = 'translateX(0)'\n}\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"What we are doing here is using Javascript to set a CSS property called \"transform\", and there we pass a CSS function called translateX() which will move the element on the x-axis. we pass '150%' to this function which will move it to the right 1.5x the width of itself. We then make flip() wait by preceeding our sleep() function with the \"await\" keyword, and pass 500 ms as an argument. Before animating our element back in we will run the rePosition() function and pass it our re-ordered array of cards."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"We want each card in our stack to be a little down, and a little to the right of the one before it. To do this we will be setting the \"top\" and \"right\" CSS properties, which position the element related to it's \"relative\" parent. We also will be using a CSS property called \"z-index\" which controls the position on the z-axis. Basically which element is on top of which other element."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Our rePosition() function takes our card array as an argument. In it we will loop through each of the elements, calculate their new position values, and then apply them. The \"top\" and \"right\" preperties will be set using a pixel value followed by 'px' (i.e. '50px'). The tricky part is that we are using the index of each array element as a multiplier in order to evenly space them on screen."}]},{"type":"element","tag":"code","props":{"code":"const rePosition = (array) => {\n\n    for (const el of array) {\n        // calculate positions by index\n        const index = array.indexOf(el)\n        const top = index * 10\n        const right = 0 - top\n\n        // apply positions\n        el.style.top = top + 'px'\n        el.style.right = right + 'px'\n        el.style.zIndex = 10 - index\n    }\n}\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"const rePosition = (array) => {\n\n    for (const el of array) {\n        // calculate positions by index\n        const index = array.indexOf(el)\n        const top = index * 10\n        const right = 0 - top\n\n        // apply positions\n        el.style.top = top + 'px'\n        el.style.right = right + 'px'\n        el.style.zIndex = 10 - index\n    }\n}\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Next we need to set a CSS property called \"transition\" which will make our \"translateX\" actually look like an animation. \"transition\" looks for changes on whatever property you give it and fills in the blanks between start and finish positions. This can be done on a stylesheet, inside of a style tag, or even inline on your element."}]},{"type":"element","tag":"code","props":{"code":"<style>\n.my-card {\n    transition: transform .5s;\n}\n</style>\n\nor\n\n<div class=\"my-card\" style=\"transition: transform .5s;\">\n</div>\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"<style>\n.my-card {\n    transition: transform .5s;\n}\n</style>\n\nor\n\n<div class=\"my-card\" style=\"transition: transform .5s;\">\n</div>\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Now finally at the bottom of the script we need to fill our empty cards array, and call rePosition() for the first time, otherwise our cards will be stacked directly on top of each other until the first animation is triggered. Here is where we need to use a onMounted or equivilant lifecycle hook to wrap around these actions, that is if you are using a virtual dom like Vue and React do, otherwise just having these at the bottom of the script should suffice."}]},{"type":"element","tag":"code","props":{"code":"onMounted(() => {\n    cards = Array.from(document.querySelectorAll('.my-card'))\n\n    rePosition(cards)\n})\n\nor\n\ncards = Array.from(document.querySelectorAll('.my-card'))\n\nrePosition(cards)\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"onMounted(() => {\n    cards = Array.from(document.querySelectorAll('.my-card'))\n\n    rePosition(cards)\n})\n\nor\n\ncards = Array.from(document.querySelectorAll('.my-card'))\n\nrePosition(cards)\n"}]}]}]},{"type":"element","tag":"quick-aside","props":{},"children":[{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"I encountered a bug that seems to be an issue with Nuxt 3. When navigating to the example page, my onMounted hook was running before the DOM was rendered. It looks like the most popular fix is to wrap everyting inside your onMounted hook, inside a setTimeout function. 100ms did it for me!"}]}]},{"type":"element","tag":"h2","props":{"id":"conclusion"},"children":[{"type":"text","value":"Conclusion"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Well that was a lot of work, but if everything went right then you have a component that can be used to display any number of cards, however if I were this to showcase several cards, I would only render the top 5 or so. Also feel free to style these any way you see fit!"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Happy coding!"}]}],"toc":{"title":"","searchDepth":2,"depth":2,"links":[{"id":"overview","depth":2,"text":"Overview"},{"id":"disclaimer","depth":2,"text":"Disclaimer"},{"id":"setting-up-the-dom","depth":2,"text":"Setting up the DOM"},{"id":"javascript-functionality","depth":2,"text":"Javascript functionality"},{"id":"conclusion","depth":2,"text":"Conclusion"}]}},"_type":"markdown","_id":"content:card-stack.md","_source":"content","_file":"card-stack.md","_extension":"md"},{"_path":"/content-mdc","_draft":false,"_partial":false,"_empty":false,"title":"Using Nuxt components in markdown.","description":"A few things I've learned about \"Mark Down Components\" aka \"MDC\".","excerpt":{"type":"root","children":[{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"For this article I will be using \"Nuxt components\" and \"Vue components\" interchangeably."}]},{"type":"element","tag":"h1","props":{"id":"what-are-markdown-components"},"children":[{"type":"text","value":"What are Markdown Components?"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"When I first looked into using \"Nuxt Content\" as a headless CMS, my main worry was that I would be prevented from using Vue components inside my markdown files, but those fears turned out to be unfounded."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"You can indeed use vue components inside markdown with an easy syntax. I have however encountered some hurdles that I will now share with you here."}]},{"type":"element","tag":"ul","props":{},"children":[{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"ContentSlot doesn't seem to work"}]},{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"forced to go back to the strange vue requirements for component naming"},{"type":"element","tag":"ul","props":{},"children":[{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"encouraged to name component \"ComponentName.vue\" but supposed to use in html like "},{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"<component-name />"}]}]},{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"when \"::componentname\" is used in markdown, \"Componentname\" is seen in terminal, leading me to believe it automatically assumes first character capital but can not distinguish past this."}]}]}]},{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"I have found this to be the correct convention."},{"type":"element","tag":"ul","props":{},"children":[{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"returning to vue standard, lower case with hyphen in markdown \"component-name\" translates to uppercase no hyphen when searching for component file \"ComponentName.vue\""}]}]}]}]},{"type":"element","tag":"quick-aside","props":{},"children":[{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Once a content component has been made, using them is easy!"}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"My "},{"type":"element","tag":"strong","props":{},"children":[{"type":"text","value":"Quick Aside"}]},{"type":"text","value":" component has been called within the markdown file like so:"}]},{"type":"element","tag":"code","props":{"code":"// Start text on same column\n::quick-aside\nText to display\n::\n\n// This won't render properly\n::quick-aside\n    Text to display\n::\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"// Start text on same column\n::quick-aside\nText to display\n::\n\n// This won't render properly\n::quick-aside\n    Text to display\n::\n"}]}]}]}]},"thumbnail":"/images/nuxt-markdown.png","slug":"content-mdc","author":"Tony Green","draft":true,"body":{"type":"root","children":[{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"For this article I will be using \"Nuxt components\" and \"Vue components\" interchangeably."}]},{"type":"element","tag":"h1","props":{"id":"what-are-markdown-components"},"children":[{"type":"text","value":"What are Markdown Components?"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"When I first looked into using \"Nuxt Content\" as a headless CMS, my main worry was that I would be prevented from using Vue components inside my markdown files, but those fears turned out to be unfounded."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"You can indeed use vue components inside markdown with an easy syntax. I have however encountered some hurdles that I will now share with you here."}]},{"type":"element","tag":"ul","props":{},"children":[{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"ContentSlot doesn't seem to work"}]},{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"forced to go back to the strange vue requirements for component naming"},{"type":"element","tag":"ul","props":{},"children":[{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"encouraged to name component \"ComponentName.vue\" but supposed to use in html like "},{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"<component-name />"}]}]},{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"when \"::componentname\" is used in markdown, \"Componentname\" is seen in terminal, leading me to believe it automatically assumes first character capital but can not distinguish past this."}]}]}]},{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"I have found this to be the correct convention."},{"type":"element","tag":"ul","props":{},"children":[{"type":"element","tag":"li","props":{},"children":[{"type":"text","value":"returning to vue standard, lower case with hyphen in markdown \"component-name\" translates to uppercase no hyphen when searching for component file \"ComponentName.vue\""}]}]}]}]},{"type":"element","tag":"quick-aside","props":{},"children":[{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Once a content component has been made, using them is easy!"}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"My "},{"type":"element","tag":"strong","props":{},"children":[{"type":"text","value":"Quick Aside"}]},{"type":"text","value":" component has been called within the markdown file like so:"}]},{"type":"element","tag":"code","props":{"code":"// Start text on same column\n::quick-aside\nText to display\n::\n\n// This won't render properly\n::quick-aside\n    Text to display\n::\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"// Start text on same column\n::quick-aside\nText to display\n::\n\n// This won't render properly\n::quick-aside\n    Text to display\n::\n"}]}]}]}],"toc":{"title":"","searchDepth":2,"depth":2,"links":[]}},"_type":"markdown","_id":"content:content-mdc.md","_source":"content","_file":"content-mdc.md","_extension":"md"},{"_path":"/create-a-nuxt-app","_draft":false,"_partial":false,"_empty":false,"title":"How to Create a Nuxt 3 App","description":"A guide to setting up Nuxt 3 with TailwindCSS, Daisyui.","excerpt":{"type":"root","children":[{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"For me, getting a new project set up is the most difficult part of the process. While I face challenges throughout the\nprocess, setting up the dependencies always seems to have the most "},{"type":"element","tag":"em","props":{},"children":[{"type":"text","value":"brick walls"}]},{"type":"text","value":" to get in my way. So when I do mange to get\na project up and running, I like to document the process."}]},{"type":"element","tag":"h2","props":{"id":"set-up"},"children":[{"type":"text","value":"Set up"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"First of all, make sure you have the latest version of npm. I am currently running version 8.3.1. Run this command in your termnimal to find out which version you have."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"npm -version"}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Next, you will want to navigate to where you keep your coding projects."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"cd C:\\coding_projects"}]}]},{"type":"element","tag":"h2","props":{"id":"installing-nuxt"},"children":[{"type":"text","value":"Installing nuxt"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"With Nuxt version 2, we had \"create-nuxt-app\", a building tool that would walk you through choices of dependencies and then build the app accordingly. Nuxt 3 however is still pretty new, and therefore we don't have that option (that I know of). So the way that worked best for me is to use nuxi in the terminal, and install your dependencies after."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"npx nuxi init your-app-name"}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"then make sure to cd into your new project."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"cd your-app-name"}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"From here you could run \"npm install\" and be up and running, but lets continue on to install tailwindcss and daisyui."}]},{"type":"element","tag":"h2","props":{"id":"what-is-tailwind"},"children":[{"type":"text","value":"What is Tailwind?"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Tailwindcss is rapidly becoming the prefered method of css styling, and for good reason. Having a seperate stylesheet where you must flip back to your html, come up with good semantic names, and create styles that are difficult to maintain especially after a few months of not reading your css, can quickly become unmanageable. Tailwind gives just about all of the css power that we love, but you put everything directly in the html element in the form of pre-made classes, like so:"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"<div class=\"flex flex-col items-center\">"}]}]},{"type":"element","tag":"h2","props":{"id":"installing-tailwind"},"children":[{"type":"text","value":"Installing tailwind"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Tailwind support is available with all major frameworks, and with the new Nuxt there is a tailwind package available. More than just putting tailwind in your Nuxt project with practically no configuration or need to install postcss, there is a handy \"tailwind viewer\" which gives you a visual reference for all of your tailwind properties."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"To install using this package, enter the following in your terminal."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"npm install -D @nuxtjs/tailwindcss"}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Then you need to add it to your modules in nuxt.config"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"export default {   modules: ['@nuxtjs/tailwindcss'] }"}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"You'll need a file called \"tailwind.config.js\" and luckily you can create one through the cli. In your terminal run the following."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"npx tailwindcss init"}]}]},{"type":"element","tag":"h2","props":{"id":"what-is-daisyui"},"children":[{"type":"text","value":"What is Daisyui?"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Daisyui is a free component library using tailwindcss. Once installed, you can then check out all of their components at their "},{"type":"element","tag":"a","props":{"href":"https://www.daisyui.com","rel":["nofollow","noopener","noreferrer"],"target":"_blank"},"children":[{"type":"text","value":"website"}]},{"type":"text","value":", copy the code, and place it in your project. What I love about Daisyui is how easy they make using color themes. They have several themes available, and you can create your own themes. You can then apply colors to your elements by setting something like \"bg-primary\" or \"bg-secondary\" so that if you want to change color schemes, just change the theme and your whole project will change!"}]},{"type":"element","tag":"h2","props":{"id":"installing-daisyui"},"children":[{"type":"text","value":"Installing Daisyui"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Installing Daisyui is easy. Just run the following in your terminal."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"npm install -D daisyui"}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Then add it to your plugins in your \"tailwind.config.js\" file."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"module.exports = {   //...   plugins: [require(\"daisyui\")], }"}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Finally, we need to run our install command"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"npm install"}]}]},{"type":"element","tag":"h2","props":{"id":"thats-it"},"children":[{"type":"text","value":"Thats it!"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"You're all set! In this guide we created a Nuxt 3 project with Tailwindcss and Daisyui"}]}]},"thumbnail":"/images/nuxtjs-logo.png","slug":"create-a-nuxt-app","author":"Tony Green","draft":false,"body":{"type":"root","children":[{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"For me, getting a new project set up is the most difficult part of the process. While I face challenges throughout the\nprocess, setting up the dependencies always seems to have the most "},{"type":"element","tag":"em","props":{},"children":[{"type":"text","value":"brick walls"}]},{"type":"text","value":" to get in my way. So when I do mange to get\na project up and running, I like to document the process."}]},{"type":"element","tag":"h2","props":{"id":"set-up"},"children":[{"type":"text","value":"Set up"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"First of all, make sure you have the latest version of npm. I am currently running version 8.3.1. Run this command in your termnimal to find out which version you have."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"npm -version"}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Next, you will want to navigate to where you keep your coding projects."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"cd C:\\coding_projects"}]}]},{"type":"element","tag":"h2","props":{"id":"installing-nuxt"},"children":[{"type":"text","value":"Installing nuxt"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"With Nuxt version 2, we had \"create-nuxt-app\", a building tool that would walk you through choices of dependencies and then build the app accordingly. Nuxt 3 however is still pretty new, and therefore we don't have that option (that I know of). So the way that worked best for me is to use nuxi in the terminal, and install your dependencies after."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"npx nuxi init your-app-name"}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"then make sure to cd into your new project."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"cd your-app-name"}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"From here you could run \"npm install\" and be up and running, but lets continue on to install tailwindcss and daisyui."}]},{"type":"element","tag":"h2","props":{"id":"what-is-tailwind"},"children":[{"type":"text","value":"What is Tailwind?"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Tailwindcss is rapidly becoming the prefered method of css styling, and for good reason. Having a seperate stylesheet where you must flip back to your html, come up with good semantic names, and create styles that are difficult to maintain especially after a few months of not reading your css, can quickly become unmanageable. Tailwind gives just about all of the css power that we love, but you put everything directly in the html element in the form of pre-made classes, like so:"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"<div class=\"flex flex-col items-center\">"}]}]},{"type":"element","tag":"h2","props":{"id":"installing-tailwind"},"children":[{"type":"text","value":"Installing tailwind"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Tailwind support is available with all major frameworks, and with the new Nuxt there is a tailwind package available. More than just putting tailwind in your Nuxt project with practically no configuration or need to install postcss, there is a handy \"tailwind viewer\" which gives you a visual reference for all of your tailwind properties."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"To install using this package, enter the following in your terminal."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"npm install -D @nuxtjs/tailwindcss"}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Then you need to add it to your modules in nuxt.config"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"export default {   modules: ['@nuxtjs/tailwindcss'] }"}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"You'll need a file called \"tailwind.config.js\" and luckily you can create one through the cli. In your terminal run the following."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"npx tailwindcss init"}]}]},{"type":"element","tag":"h2","props":{"id":"what-is-daisyui"},"children":[{"type":"text","value":"What is Daisyui?"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Daisyui is a free component library using tailwindcss. Once installed, you can then check out all of their components at their "},{"type":"element","tag":"a","props":{"href":"https://www.daisyui.com","rel":["nofollow","noopener","noreferrer"],"target":"_blank"},"children":[{"type":"text","value":"website"}]},{"type":"text","value":", copy the code, and place it in your project. What I love about Daisyui is how easy they make using color themes. They have several themes available, and you can create your own themes. You can then apply colors to your elements by setting something like \"bg-primary\" or \"bg-secondary\" so that if you want to change color schemes, just change the theme and your whole project will change!"}]},{"type":"element","tag":"h2","props":{"id":"installing-daisyui"},"children":[{"type":"text","value":"Installing Daisyui"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Installing Daisyui is easy. Just run the following in your terminal."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"npm install -D daisyui"}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Then add it to your plugins in your \"tailwind.config.js\" file."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"module.exports = {   //...   plugins: [require(\"daisyui\")], }"}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Finally, we need to run our install command"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"element","tag":"code-inline","props":{},"children":[{"type":"text","value":"npm install"}]}]},{"type":"element","tag":"h2","props":{"id":"thats-it"},"children":[{"type":"text","value":"Thats it!"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"You're all set! In this guide we created a Nuxt 3 project with Tailwindcss and Daisyui"}]}],"toc":{"title":"","searchDepth":2,"depth":2,"links":[{"id":"set-up","depth":2,"text":"Set up"},{"id":"installing-nuxt","depth":2,"text":"Installing nuxt"},{"id":"what-is-tailwind","depth":2,"text":"What is Tailwind?"},{"id":"installing-tailwind","depth":2,"text":"Installing tailwind"},{"id":"what-is-daisyui","depth":2,"text":"What is Daisyui?"},{"id":"installing-daisyui","depth":2,"text":"Installing Daisyui"},{"id":"thats-it","depth":2,"text":"Thats it!"}]}},"_type":"markdown","_id":"content:create-a-nuxt-app.md","_source":"content","_file":"create-a-nuxt-app.md","_extension":"md"},{"_path":"/pivot-table-journey","_draft":false,"_partial":false,"_empty":false,"title":"Parsing Data From a Five Column Pivot Table.","description":"A tale of research and greater understanding.","excerpt":{"type":"root","children":[{"type":"element","tag":"h2","props":{"id":"the-project"},"children":[{"type":"text","value":"The Project"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"I am building a project that has two tables with a many-to-many relationship in a MySql database. To do this I needed a third table, called a \"pivot\" table. Setting it up, establishing the relationship in my models, creating the migrations, these were all pretty straight-forward. Once I recieved the data on the front-end, I had some work to do and I learned a lot about the frameworks that I've been using in the process."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"This project is a recipe app (From what I hear, we've all made one) that will also provide users on an Auto Immune Protocol diet a way to track which foods they introduce back into their lives. I am building it with Vue 3 with Laravel as it's API, an AIP-API if you will."}]},{"type":"element","tag":"h2","props":{"id":"the-back-end"},"children":[{"type":"text","value":"The Back End"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"I love PHP and I have no interest in using WordPress, I must hate Money or something. Laravel is a fantastic PHP framework that I have used on a few projects, but this is my first real project using it as just an API."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"I have a Recipe table, which stores the name, image, directions, etc. and I have an Ingredients table which stores individual ingredients. I could have just put the ingredients in the recipe table as an array of strings, but the functionality of the app requires these ingredients to be stored and tracked among the recipes. Enter the pivot table."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"A pivot table typically contains an ID and the ID's of two rows from different tables, facilitating a many-to-many relationship between them. My pivot table has two extra columns, 'qty' and 'unit', because these values are vary among recipes. Like I said, the set up went fine. I have the Laravel developers to thank I think."}]},{"type":"element","tag":"quick-aside","props":{},"children":[{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Naming the pivot table \"ingredient_recipe_table\" is needed for the magic to work, I think."}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Here is the heavy lifter in my RecipeController's \"create\" method."}]},{"type":"element","tag":"code","props":{"code":"$recipe->ingredients()->attach($ingredient, ['qty' => $ing->qty, 'unit' => $ing->unit]);\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"$recipe->ingredients()->attach($ingredient, ['qty' => $ing->qty, 'unit' => $ing->unit]);\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"The attach() method seamlessly establishes the recipe-ingredient relationship and can take an associative array to set other values."}]},{"type":"element","tag":"h2","props":{"id":"the-front-end"},"children":[{"type":"text","value":"The Front End"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"I'm using Vue 3 to build my UI, and I love it. I miss having Nuxt.js take care of all the little things, but I learned alot by using vue-router and I don't want to get spoiled too much by Nuxt."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"On the client side I have a catch-all route that will display a single recipe, the ingredients listed along with their qty and unit values. Here's where the problem occured. When this component retrieves the data from the API, all the ingredients are objects set as an attribute of the recipe, which is good, but the extra special pivot data is nested another level inside each ingredient. I know I could have just worked with the nested data, but imagining all the extra code in my components gave me the creeps so I spent the extra time to rearrange the data on the server before it's sent out."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"A query that returns one model instance comes in a kind of model object, where when multiple instances come as Laravel \"Collection\" and these are awesome. They really tripped me up in the past, and they did here too because they are not an associative array but they sure do look like one. Collections are immutable, except when using certain Collection methods, although many of them just return a new Collection. Even though I am getting only one Recipe from the database, I think a Collection works best for what I am trying to accomplish so I used the collect() method on my query."}]},{"type":"element","tag":"code","props":{"code":"$res = collect(Recipe::with('ingredients')->find($id));\n\n$ingredients = [];\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"$res = collect(Recipe::with('ingredients')->find($id));\n\n$ingredients = [];\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"What I needed was to extract the array of ingredients from the collection, iterate over it and copy the two values I want from the nested \"pivot\" array directly into the ingredient, and make it stick. At first I tried pulling the ingredients out with the Collection->pull() method, but if I remember correctly that just returned another immutable collection. I also discovered that a PHP foreach loop actually iterates over a copy of the array, so my changes weren't saved as I expected. So instead I left the data where it was in the collection, iterated over the ingredients directly and saved each one in a new array as changes were made, and removed the pivot array."}]},{"type":"element","tag":"code","props":{"code":" foreach ($res['ingredients'] as $ingredient) {\n\n                    $pivot = $ingredient['pivot'];\n                    unset($ingredient['pivot']);\n\n                    $ingredient['qty'] = $pivot['qty'];\n                    $ingredient['unit'] = $pivot['unit'];\n\n                    array_push($ingredients, $ingredient);\n                }\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":" foreach ($res['ingredients'] as $ingredient) {\n\n                    $pivot = $ingredient['pivot'];\n                    unset($ingredient['pivot']);\n\n                    $ingredient['qty'] = $pivot['qty'];\n                    $ingredient['unit'] = $pivot['unit'];\n\n                    array_push($ingredients, $ingredient);\n                }\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Next I used another magic method provided by Laravel Collections."}]},{"type":"element","tag":"code","props":{"code":"$res->put('ingredients', $ingredients);\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"$res->put('ingredients', $ingredients);\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Collection->put() will set an attribute, overwriting one if the key already exists. This worked perfectly for me. I used this method to save the new array to the \"ingredients\" attribute in my Collection. Now when my Vue component gets the recipe data, it is much more easily incorporated into the DOM."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"I'm pretty sure this was more work than just going down one more level on the JS object, but I could just imagine something like this being confusing to a front-end developer and wishing that the API guys would just clean up their data."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Now that's what I call a full-stack experience!"}]}]},"thumbnail":"/images/database-visualization.png","slug":"pivot-table-journey","author":"Tony Green","draft":false,"body":{"type":"root","children":[{"type":"element","tag":"h2","props":{"id":"the-project"},"children":[{"type":"text","value":"The Project"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"I am building a project that has two tables with a many-to-many relationship in a MySql database. To do this I needed a third table, called a \"pivot\" table. Setting it up, establishing the relationship in my models, creating the migrations, these were all pretty straight-forward. Once I recieved the data on the front-end, I had some work to do and I learned a lot about the frameworks that I've been using in the process."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"This project is a recipe app (From what I hear, we've all made one) that will also provide users on an Auto Immune Protocol diet a way to track which foods they introduce back into their lives. I am building it with Vue 3 with Laravel as it's API, an AIP-API if you will."}]},{"type":"element","tag":"h2","props":{"id":"the-back-end"},"children":[{"type":"text","value":"The Back End"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"I love PHP and I have no interest in using WordPress, I must hate Money or something. Laravel is a fantastic PHP framework that I have used on a few projects, but this is my first real project using it as just an API."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"I have a Recipe table, which stores the name, image, directions, etc. and I have an Ingredients table which stores individual ingredients. I could have just put the ingredients in the recipe table as an array of strings, but the functionality of the app requires these ingredients to be stored and tracked among the recipes. Enter the pivot table."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"A pivot table typically contains an ID and the ID's of two rows from different tables, facilitating a many-to-many relationship between them. My pivot table has two extra columns, 'qty' and 'unit', because these values are vary among recipes. Like I said, the set up went fine. I have the Laravel developers to thank I think."}]},{"type":"element","tag":"quick-aside","props":{},"children":[{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Naming the pivot table \"ingredient_recipe_table\" is needed for the magic to work, I think."}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Here is the heavy lifter in my RecipeController's \"create\" method."}]},{"type":"element","tag":"code","props":{"code":"$recipe->ingredients()->attach($ingredient, ['qty' => $ing->qty, 'unit' => $ing->unit]);\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"$recipe->ingredients()->attach($ingredient, ['qty' => $ing->qty, 'unit' => $ing->unit]);\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"The attach() method seamlessly establishes the recipe-ingredient relationship and can take an associative array to set other values."}]},{"type":"element","tag":"h2","props":{"id":"the-front-end"},"children":[{"type":"text","value":"The Front End"}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"I'm using Vue 3 to build my UI, and I love it. I miss having Nuxt.js take care of all the little things, but I learned alot by using vue-router and I don't want to get spoiled too much by Nuxt."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"On the client side I have a catch-all route that will display a single recipe, the ingredients listed along with their qty and unit values. Here's where the problem occured. When this component retrieves the data from the API, all the ingredients are objects set as an attribute of the recipe, which is good, but the extra special pivot data is nested another level inside each ingredient. I know I could have just worked with the nested data, but imagining all the extra code in my components gave me the creeps so I spent the extra time to rearrange the data on the server before it's sent out."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"A query that returns one model instance comes in a kind of model object, where when multiple instances come as Laravel \"Collection\" and these are awesome. They really tripped me up in the past, and they did here too because they are not an associative array but they sure do look like one. Collections are immutable, except when using certain Collection methods, although many of them just return a new Collection. Even though I am getting only one Recipe from the database, I think a Collection works best for what I am trying to accomplish so I used the collect() method on my query."}]},{"type":"element","tag":"code","props":{"code":"$res = collect(Recipe::with('ingredients')->find($id));\n\n$ingredients = [];\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"$res = collect(Recipe::with('ingredients')->find($id));\n\n$ingredients = [];\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"What I needed was to extract the array of ingredients from the collection, iterate over it and copy the two values I want from the nested \"pivot\" array directly into the ingredient, and make it stick. At first I tried pulling the ingredients out with the Collection->pull() method, but if I remember correctly that just returned another immutable collection. I also discovered that a PHP foreach loop actually iterates over a copy of the array, so my changes weren't saved as I expected. So instead I left the data where it was in the collection, iterated over the ingredients directly and saved each one in a new array as changes were made, and removed the pivot array."}]},{"type":"element","tag":"code","props":{"code":" foreach ($res['ingredients'] as $ingredient) {\n\n                    $pivot = $ingredient['pivot'];\n                    unset($ingredient['pivot']);\n\n                    $ingredient['qty'] = $pivot['qty'];\n                    $ingredient['unit'] = $pivot['unit'];\n\n                    array_push($ingredients, $ingredient);\n                }\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":" foreach ($res['ingredients'] as $ingredient) {\n\n                    $pivot = $ingredient['pivot'];\n                    unset($ingredient['pivot']);\n\n                    $ingredient['qty'] = $pivot['qty'];\n                    $ingredient['unit'] = $pivot['unit'];\n\n                    array_push($ingredients, $ingredient);\n                }\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Next I used another magic method provided by Laravel Collections."}]},{"type":"element","tag":"code","props":{"code":"$res->put('ingredients', $ingredients);\n"},"children":[{"type":"element","tag":"pre","props":{},"children":[{"type":"element","tag":"code","props":{"__ignoreMap":""},"children":[{"type":"text","value":"$res->put('ingredients', $ingredients);\n"}]}]}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Collection->put() will set an attribute, overwriting one if the key already exists. This worked perfectly for me. I used this method to save the new array to the \"ingredients\" attribute in my Collection. Now when my Vue component gets the recipe data, it is much more easily incorporated into the DOM."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"I'm pretty sure this was more work than just going down one more level on the JS object, but I could just imagine something like this being confusing to a front-end developer and wishing that the API guys would just clean up their data."}]},{"type":"element","tag":"p","props":{},"children":[{"type":"text","value":"Now that's what I call a full-stack experience!"}]}],"toc":{"title":"","searchDepth":2,"depth":2,"links":[{"id":"the-project","depth":2,"text":"The Project"},{"id":"the-back-end","depth":2,"text":"The Back End"},{"id":"the-front-end","depth":2,"text":"The Front End"}]}},"_type":"markdown","_id":"content:pivot-table-journey.md","_source":"content","_file":"pivot-table-journey.md","_extension":"md"}]