nolros

nolros

Member Since 5 Years Ago

Experience Points
89,610
Total
Experience

390 experience to go until the next level!

In case you were wondering, you earn Laracasts experience when you:

  • Complete a lesson — 100pts
  • Create a forum thread — 50pts
  • Reply to a thread — 10pts
  • Leave a reply that is liked — 50pts
  • Receive a "Best Reply" award — 500pts
Lessons Completed
397
Lessons
Completed
Best Reply Awards
29
Best Reply
Awards
  • start-engines Created with Sketch.

    Start Your Engines

    Earned once you have completed your first Laracasts lesson.

  • first-thousand Created with Sketch.

    First Thousand

    Earned once you have earned your first 1000 experience points.

  • 1-year Created with Sketch.

    One Year Member

    Earned when you have been with Laracasts for 1 year.

  • 2-years Created with Sketch.

    Two Year Member

    Earned when you have been with Laracasts for 2 years.

  • 3-years Created with Sketch.

    Three Year Member

    Earned when you have been with Laracasts for 3 years.

  • 4-years Created with Sketch.

    Four Year Member

    Earned when you have been with Laracasts for 4 years.

  • 5-years Created with Sketch.

    Five Year Member

    Earned when you have been with Laracasts for 5 years.

  • school-session Created with Sketch.

    School In Session

    Earned when at least one Laracasts series has been fully completed.

  • welcome-newcomer Created with Sketch.

    Welcome To The Community

    Earned after your first post on the Laracasts forum.

  • full-time-student Created with Sketch.

    Full Time Learner

    Earned once 100 Laracasts lessons have been completed.

  • pay-it-forward Created with Sketch.

    Pay It Forward

    Earned once you receive your first "Best Reply" award on the Laracasts forum.

  • subscriber-token Created with Sketch.

    Subscriber

    Earned if you are a paying Laracasts subscriber.

  • lifer-token Created with Sketch.

    Lifer

    Earned if you have a lifetime subscription to Laracasts.

  • lara-evanghelist Created with Sketch.

    Laracasts Evangelist

    Earned if you share a link to Laracasts on social media. Please email [email protected] with your username and post URL to be awarded this badge.

  • chatty-cathy Created with Sketch.

    Chatty Cathy

    Earned once you have achieved 500 forum replies.

  • lara-veteran Created with Sketch.

    Laracasts Veteran

    Earned once your experience points passes 100,000.

  • 10k-strong Created with Sketch.

    Ten Thousand Strong

    Earned once your experience points hits 10,000.

  • lara-master Created with Sketch.

    Laracasts Master

    Earned once 1000 Laracasts lessons have been completed.

  • laracasts-tutor Created with Sketch.

    Laracasts Tutor

    Earned once your "Best Reply" award count is 100 or more.

  • laracasts-sensei Created with Sketch.

    Laracasts Sensei

    Earned once your experience points passes 1 million.

  • top-50 Created with Sketch.

    Top 50

    Earned once your experience points ranks in the top 50 of all Laracasts users.

Level 18
89,610 XP
Dec
06
1 week ago
Activity icon

Replied to Code Review

@paduraruionutandrei got it. Here is the structure. Note: this would change if there is multi relationship i.e. if the same tasks belongs to multiple buckets or a bucket can belong to multiple projects


 class Company extends Model
    {
        /**
         * Company can have many projects
         */
        public function projects()
        {
            return $this->belongsToMany(Project::class, 'company_project');
        }

    }
    class Project extends Model
    {
        /**
         * assumes company_id in project table
         * project belongs to company
         */
        public function company()
        {
            return $this->belongsTo(Company::class);
        }

        /**
         * project has many tasks
         */
        public function buckets()
        {
            return $this->hasMany(Bucket::class);
        }

    }

    class Bucket extends Model
    {
        /**
         * assumes project_id in bucket table
         * task belongs to project
         */
        public function project()
        {
            return $this->belongsTo(Project::class);
        }

        /**
         * bucket  belongs to project
         */
        public function tasks()
        {
            return $this->hasMany(Bucket::class);
        }

    }

    class Task extends Model
    {
        /**
         * assumes bucket_id in tasks table
         * task belongs to project
         */
        public function bucket()
        {
            return $this->belongsTo(Bucket::class);
        }
    }


Activity icon

Replied to Belongs To Many Count Based On Created_at And Task_id In Laravel

@princeoo7 a lot to what you asking and many ways to solve, but I would suggest looking local and global scopes. Here is an example. Note: this has not been tested, but more directional


<?php
    
    
    namespace App\Sandbox;
    
    use Carbon\Carbon;
    use Illuminate\Database\Eloquent\Model;
    
    class User extends Model
    {
    
        /**
         * @return mixed
         */
        public function tasksCompletedCount()
        {
            return $this->tasks()->completed()->get()->count();
        }
    
        /**
         * @return mixed
         */
        public function tasksCompleted()
        {
            return $this->tasks()->completed()->orderBy('end_at')->get();
        }
    
        /**
         * @param Carbon $date
         * @return mixed
         */
        public function tasksCompletedByDueDate(Carbon $date)
        {
            return $this->tasks()->dueDate($date)->orderBy('end_at')->get();
        }
    
        /**
         * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
         */
        public function tasks()
        {
            return $this->belongsToMany(Task::class,'user_task');
        }
    
    }
    
    class Task extends Model
    {
    
        /**
         * @param $query
         * @return mixed
         */
        public function scopeCompleted($query)
        {
            return $query->where('completed', 1);
        }
    
        /**
         * @param $query
         * @param Carbon $date
         * @return mixed
         */
        public function scopeDueDate($query, Carbon $date)
        {
            return $query->where('end_at', $date);
        }
    
        /**
         * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
         */
        public function users()
        {
            return $this->belongsToMany(User::class,'user_task');
        }
    
    }


Activity icon

Replied to Code Review

@paduraruionutandrei have you looked at Laravel Passport for APIs, unsure as to your requirements but purpose-built for APIs e.g. tokens, etc. https://laravel.com/docs/6.x/passport I assume you mean an API that can be consumed by other applications? Relations are data structures and have little to do with auth or access. If you are looking to share data with functionality outside of your own app then API like passport would work.

Activity icon

Replied to One To Many

@davy_yg a blog post belongsTo a category. A blog hasMany comments, and hasMany posts. Therefore, from top down, a category has many posts and post belongs to a category (could belong to many tags or categories, but let's not complicate that now).


     class Category extends Model
     {
    
        public function posts()
        {
            return $this->hasMany(Post::class);
        }
    }
    
     class Post extends Model
    {
         /**
         * assumes you category_id in the posts table
         */
        public function category()
        {
            return $this->belongsTo(Category::class);
        }
    
        /**
         * assumes you blog_id in the posts table
         */
        public function blog()
        {
            return $this->belongsTo(Blog::class);
        }
    
    
        public function comments()
        {
            return $this->hasMany(Comments::class);
        }
    
    }
    
    class Comments extends Model
    {
        /**
         * assumes you post_id in the comments table
         */
        public function post()
        {
            return $this->belongsTo(Post::class);
        }
    }
    
    class Blog extends Model
    {
        public function posts()
        {
            return $this->hasMany(Post::class);
        }
    }

Oct
25
1 month ago
Activity icon

Replied to How To Calculate In Model

@fareedr assuming you have a DB column called star with some value then use Laravel get mutation.

https://laracasts.com/discuss/channels/laravel/how-to-calculate-in-model

To define an accessor, create a getFooAttribute method on your model where Foo is the "studly" cased name of the column you wish to access.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Get the user's first name.
     *
     * @param  string  $value
     * @return string
     */
    public function getStarAttribute($value)
    {
        return // whatever calculation you need to do. $value will be the value form the DB column.
    }
}

Activity icon

Replied to How To Pass Data With Vue Between Pages

@chrisgrim example of the page setup. The portal and the navigation AND/OR search would be on each page so there is no need to pass data.

Example: home or app.blade.php


<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <!-- CSRF Token -->
    <meta name="csrf-token" content="{!!csrf_token()!!}"/>
    <script>
        window.Laravel = {!! json_encode([ 'csrfToken' => csrf_token()]) !!};
    </script>

    <!-- Fonts -->
    <link href="https://fonts.googleapis.com/css?family=Poppins:200,300,400" rel="stylesheet">

    <!-- Styles -->
    <link rel="stylesheet" href="{!!  mix('css/app.css') !!}" type="text/css">

    <!-- Meta Boilerplate -->

</head>
    <body>
        <main id="app">
            <navigation-search></navigation-search>
            <portal-target name="searchResults" class="fixed w-full w-auto"></portal-target>

            @yield('content')
            @include('pages.partials.footer')
        </main>

        <script src="{!! mix('js/app.js') !!}"></script>
    </body>
</html>


The Vue Comp. Note I have not tested it and it would require CSS work, etc. Best to combine this with you nav so that search and Nav are on all pages.


<template>
    <div class="relative flex flex-row justify-center w-full">

        <!-- This is an example search button-->
        <div class="relative flex flex-row justify-end items-center content-center">
            <button @click.stop="toggleSearch"
                class="search--btn">
                <svg class="search--btn-svg" xmlns="http://www.w3.org/2000/svg"
                     viewBox="0 0 24 24" width="24" height="24">
                    <g fill="none" stroke="currentColor" stroke-linecap="round"
                       stroke-linejoin="round">
                        <circle cx="10.5" cy="10.6" r="7.3"></circle>
                        <path d="M15.6 15.8l4.9 4.8"></path>
                    </g>
                </svg>
            </button>
        </div>

        <!-- This is an example search form / btn-->
        <div v-if="active"
             class="relative flex flex-row flex-wrap">
            <form method="POST"
                  autocomplete="off"
                  name="search"
                  action="/api/post/search"
                  @submit.prevent="onSubmit"
                  class="relative">

                <input type="text"
                       autocomplete="off"
                       v-model="search"
                       id="search"
                       v-focus
                       class="form--dark__input"
                       placeholder="Enter Search Term Here"/>

                <button type="submit">
                    <span class="relative inline-block w-full uppercase">Search</span>
                </button>

            </form>
        </div>

        <!-- Search results would go here which will show up in home blade-->
        <portal to="searchResults"
                v-if="active">
            <!-- Click anywhere on teh ul will toggle search off-->

            <ul @click.stop="toggleSearch"
                class="relative flex flex-row flex-wrap">
                <li v-for="(result,key) in results"
                    class="relative inline-block w-1/3"
                    :id="result.id">
                    <!-- The rest of your HTML goes here-->
                </li>
            </ul>
        </portal>
    </div>
</template>

<script>

    export default {
        name: "NavigationSearch",

        props: {
            searchTerm: {
                type: String,
                required: false
            },
        },
        directives: {
            focus: {
                // directive to ensure focus is set in input if hidden
                inserted: function (el) {
                    el.focus()
                }
            }
        },
        data: function () {
            return {
                searchState: false,
                search: {},
                results: {},
                active: false,
                searchTerms: {},
            }
        },
        computed: {
            hasResults() {
                return _.size(this.results);
            },
            toggleSearch() {
                this.active =! this.active;
            },
        },
        methods: {
            getPosts() {
                // Note can add a throttle or debounce
                let self = this;

                if (this.search.trim() !== '') {
                    return new Promise((resolve, reject) => {
                        axios.post('/search', {search: self.search})
                            .then(response => {
                                resolve((self.results = response.data))
                            })
                            .catch(error => {
                                reject(error.response.data);
                            });
                    });
                }
            },
            selectPost: function (keyCode) {
                // If down arrow key is pressed
                if (keyCode === 40 && this.count < this.results.length) {
                    this.count++;
                }
                // If up arrow key is pressed
                if (keyCode === 38 && this.count > 1) {
                    this.count--;
                }
                // If enter key is pressed
                if (keyCode === 13) {
                    // Go to selected post
                    // document.getElementById(this.count).childNodes[0].click();
                }
            },
        },

        mounted: function () {
            let self = this;

            /*
            * We check if a search term has been injected into the component and if so then that is the search term.
            *  example - we redirect from teh server with a term. We then get psots / products, etc
            */
            if (!_.isEmpty(this.searchTerm)) {
                this.search = this.searchTerm;
                this.getPosts();
            }

            // To clear  search widget when click on body
            document.body.addEventListener('click', function (e) {
                self.clearData(e);
            });

            // check whether arrow keys are pressed
            document.getElementById('search').addEventListener('keydown', function (e) {

                if (_.includes([37, 38, 39, 40, 13], e.key)) {
                    // To prevent cursor from moving left or right in text input
                    if (e.key === 38 || e.key === 40) {
                        e.preventDefault();
                    }
                    // If post list is cleared and search input is not empty then call ajax again on down arrow key press
                    if (e.key === 40 && self.results === "") {
                        return self.getPosts();
                    }
                    // select the post / result item
                    self.selectPost(e.key);

                } else {
                    // check to see if the user is backspacing or moving arrow left or id they press eneter
                    // 8 bs, 37 left arrow, del 46
                    if ((e.key === 8 || e.key === 46) && self.keyCount > 1) {
                        self.keyCount--;
                    } else if (e.key !== 16) {
                        self.keyCount++;
                    }

                    // after 3 valid keydowns then we conduct a search for the terms
                    if (self.keyCount >= 3) {
                        self.getPosts();
                    }
                }


            });
        },
    }
</script>




Any Laravel page that extends app.blade.php will have search. Example, blog or products.blade.php


@extends('app')
@section('content')
{{--        Any HTML page that extends from the app.blade.php--}}
@endsection

Activity icon

Replied to How To Pass Data With Vue Between Pages

@chrisgrim you keep mentioning the data. What data are you needing to pass through / along with redirect and why? Are you redirecting to same app or different app?

Oct
24
1 month ago
Activity icon

Replied to Get All Descendants Of Parent In User Model .

@mostafalaravel here is a different approach


<?php

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Products\Models\Product;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->unsignedBigInteger('parent_id')->nullable();
            $table->string('name');
            $table->string('email');
            $table->timestamps();


            $table->foreign('parent_id')->references('id')->on('users')
                ->onDelete('restrict')
                ->onUpdate('cascade');
        });

    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function(Blueprint $table) {
            $table->dropForeign('users_parent_id_foreign');
        });

        Schema::dropIfExists('users');

    }
}


class User extends Model
{
    
    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function descendants()
    {
        return $this->hasMany(User::class,'parent_id', 'id');
    }
}


Team leaders will all be root and as such their parent_id will null or you could have a role property as well. Then you would create a loop depending on how deep you want to go


$getChildren = $this->users->whereNull('parent_id')->with(['descendants'])->get();


// OR create a recursive function that keeps call for each team leader until the children descendants() returns an empty collection



Activity icon

Replied to How To Pass Data With Vue Between Pages

@chrisgrim a redirect in JS is as simple as window.location.href = and in vue router it is router.go(path)

I started working on a SPA search component, porting some JS, etc. loads of cleanup required along with Vue features e.g. key events, etc. but this gets you close to what you looking to do for an SPA and autocomplete. I use Vue Portal to push the results to "body" of the page to minimize CSS etc. Also using Tailwind. I'll keep cleaning up this week and then post it a codepen or something


<template>
    <div class="relative flex flex-row justify-center w-full dark__form--input-group">
        <div class="control relative flex flex-row flex-wrap justify-center items-end w-full dark__form--control">
            <input type="text"
                   autocomplete="off"
                   v-model="search"
                   id="search"
                   v-focus
                   class="form--dark__input"
                   placeholder="Enter Search Term Here"/>

            <!-- Vue Search List Start-->
            <portal to="searchMain">
                <div @click.stop="toggleSearch($event)"
                     class="core-base--width screen--overlay active search-active flex flex-row flex-wrap justify-center items-start content-start pattern noise">
                    <ul v-cloak
                        class="search--list relative w-full flex flex-row flex-wrap justify-center items-start content-start z-10 list-reset"
                        v-if="results">

                        <li v-for="(result,key) in filterResults"
                            :id="result.id"
                            class="relative f--col-4 pattern diagonal-02 noise--hover p-4 mb-2 h--300 "
                            :class="[(key + 1 === count) ? activeClass : '', menuItem,getHeight]">

                            <a class="search__item--link relative w-full justify-center items-top flex-wrap z-20"
                               @click.stop="toggleSearch($event)"
                               :title="result.name"
                               :href="result.url">

                                <div class="search__content--container">
                                    <div
                                        class="relative w-full flex flex-row justify-center items-center z-20 h-auto pb-4">
                                        <img class="relative inline-block image--square-050"
                                             :src="result.image.url"
                                             :alt="result.image.alt"
                                             :title="result.image.name">
                                    </div>
                                    <p class="relative w-full font__osw f--light f--21 color--e2 text-center no-underline select-none uppercase"
                                       v-text="result.type">
                                    </p>

                                    <div class="search__content--group">
                                        <h6 v-html="highlight(result.name)"
                                            class="relative w-full f--regular f--space-03 color--e2 f--pop text-center select-none pt-4 pb-2 f--21">
                                        </h6>
                                    </div>
                                    <p class="relative w-full f--14 f--thin color--d1 f--pop text-center select-none pb-4">
                                        {{result.description | truncate(200,'...')}}
                                    </p>
                                </div>
                            </a>
                        </li>
                    </ul>
                </div>
            </portal>
            <!-- Vue Search List End-->
        </div>
    </div>
    <!-- search box container ends  -->
</template>

<script>

    export default {
        name: "SearchBox",

        props: {
            searchTerm: {
                type: String,
                required: false
            },
        },
        directives: {
            focus: {
                // directive to ensure focus is set in input if hidden
                inserted: function (el) {
                    el.focus()
                }
            }
        },
        data: function () {
            return {
                searchState: false,
                search: '',
                count: 0,
                width: 0,
                menuItem: 'menu-item',
                activeClass: 'active',
                keywords: null,
                results: [],
                searchTerms: {},
                searchMatches: {},
                keyCount: 0,
                responsive: {
                    lg: 12,
                    md: 8,
                    sm: 6,
                    xs: 3
                }
            }
        },
        computed: {
            getHeight() {
                return "h--" + this.$mq;
            },
            filterResults() {
                return _.slice(this.results, 0, 8);
            },
        },
        methods: {
            highlight(text) {
                let searchables = this.search.split(" ");

                if (_.size(searchables)) {
                    text = findReplace(text, searchables, "<span class='search--highlighted'>$&</span>");
                }

                return text;
            },
            toggleSearch(e) {
                this.results = '';
                this.count = 0;
                this.keyCount = 0;
                this.searchMatches = {};
                Event.fire("toggle-search", true);
            },

            clearData: function (e) {
                if (e.target.id !== 'search') {
                    this.results = '';
                    this.count = 0;
                    this.keyCount = 0;
                    this.searchMatches = {};
                }
            },

            getSearchTerms: function (resource) {
                let vm = this;

                this.axiosService.fetch(resource)
                    .then(response => {
                        vm.searchTerms = response.data;
                    })
                    .catch(error => {
                    });
            },
            getPosts() {
                // Note can add a throttle or debounce
                let self = this;

                if (this.search.trim() !== '') {
                    return new Promise((resolve, reject) => {
                        axios.post('/search', {search: self.search})
                            .then(response => {
                                resolve((self.results = response.data))
                            })
                            .catch(error => {
                                reject(error.response.data);
                            });
                    });
                }
            },
            selectPost: function (keyCode) {
                // If down arrow key is pressed
                if (keyCode === 40 && this.count < this.results.length) {
                    this.count++;
                }
                // If up arrow key is pressed
                if (keyCode === 38 && this.count > 1) {
                    this.count--;
                }
                // If enter key is pressed
                if (keyCode === 13) {
                    // Go to selected post
                    // document.getElementById(this.count).childNodes[0].click();
                }
            },
        },

        mounted: function () {
            let self = this;

            /*
            * We check if a search term has been injected into the component and if so then that is the search term.
            *  example - we redirect from teh server with a term. We then get psots / products, etc
            */
            if (!_.isEmpty(this.searchTerm)) {
                this.search = this.searchTerm;
                this.getPosts();
            }

            // To clear  search widget when click on body
            document.body.addEventListener('click', function (e) {
                self.clearData(e);
            });

            // check whether arrow keys are pressed
            document.getElementById('search').addEventListener('keydown', function (e) {

                if (_.includes([37, 38, 39, 40, 13], e.key)) {
                    // To prevent cursor from moving left or right in text input
                    if (e.key === 38 || e.key === 40) {
                        e.preventDefault();
                    }
                    // If post list is cleared and search input is not empty then call ajax again on down arrow key press
                    if (e.key === 40 && self.results === "") {
                        return self.getPosts();
                    }
                    // select the post / result item
                    self.selectPost(e.key);

                } else {
                    // check to see if the user is backspacing or moving arrow left or id they press eneter
                    // 8 bs, 37 left arrow, del 46
                    if ((e.key === 8 || e.key === 46) && self.keyCount > 1) {
                        self.keyCount--;
                    } else if (e.key !== 16) {
                        self.keyCount++;
                    }

                    // after 3 valid keydowns then we conduct a search for the terms
                    if (self.keyCount >= 3) {
                        self.getPosts();
                    }
                }


            });

            this.$nextTick(this.init());

        },
    }
</script>




Oct
23
1 month ago
Activity icon

Replied to Disable Back To Previouse Route For Specific Time

@ahkeravi example


<button type="submit" class="btn btn-default" name="roll" <?php echo isset($_POST["roll"]) ? "disabled" : "";>
Roll Branch 
</button> 

Activity icon

Replied to How To Pass Data With Vue Between Pages

@chrisgrim options are as follows:

  1. save it in php session
  2. save it in HTML Session Storage
  3. not recommended - depending on the size you can pass it along in the url and then return the segments / data when your redirect back from server
  4. not recommended - pass it up as parameters or data and then just return it
  5. use Vue router which will allow you to navigate through Vue views and you can then redirect to server as required.
Activity icon

Replied to Multi Model, Abstract Model, Or Something Else?

@thepoet444

The relations and models would look something like below.


class Fleet extends Model
{
    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function wings()
    {
        return $this->hasMany(Wing::class);
    }

}

class Wing extends Model
{
    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function squads()
    {
        return $this->hasMany(Squad::class);
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function fleet()
    {
        return $this->belongsTo(Fleet::class);
    }

}

class Squad extends Model
{
    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function wing()
    {
        return $this->belongsTo(Wing::class);
    }

}


Example, query would be


class Fleet extends Model
{

    /**
     * Scope a query to only include fleets of a given co.
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $query
     * @param  mixed  $co
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeOfCommandingOfficer($query, $co = "Adm. Foo Bar")
    {
        return $query->where('co', $co)->with(['wings.squads']);
    }
    
    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function wings()
    {
        return $this->hasMany(Wing::class);
    }

}


class ExampleController {

    public function getFleetOfCo($co = "Adm. Foo Bar")
    {
        return Fleet::OfCommandingOfficer()->get();

    }
}

Activity icon

Replied to Cache Eloquent Queries - Boot OR Service Provider OR ...

@bobbybouwmann as a side comment. I cache OS, PHP, routes, queries and have written my own Vue Session Storage cache which results in sites loading in 1 - 1.5 seconds thereby scoring a perfect 100% on both Google and GTMetrix pagespeed tests.

Activity icon

Replied to Cache Eloquent Queries - Boot OR Service Provider OR ...

@bobbybouwmann I'm trying to cache the all the product queries for 24 hour period. This would be somewhat static content for a website. Why? caching the product and features helps with SEO page speed, which works In the case of a B2B company (e.g. software company or marketing agency) where their products or services don't often change often. Obviously this approach wouldn't work well for products with an ecommerce site.

Activity icon

Started a new Conversation Cache Eloquent Queries - Boot OR Service Provider OR ...

There must be a better way of caching teh model. Lets assume 5 product families with 5 products in each product family with approx 20 product features for each product i.e. a family will have 5 products and each will have 20 features so a total of 100 features for each family. So the relation will be product_family -> hasMany products -> hasMany features.

Example, family = SEO and products Audit, Local SEO, eCommerce SEO. Mobile SEO, and Voice SEO. I want to cache the family with products and features or each product with features.

The example below works, but doesn't seem elegant.

Other options might include:

  1. A service provider (ProductsServiceProvider) that caches all requests on boot
  2. A Model boot trait
  3. A get attribute mutator

Thoughts?

<?php


namespace App\Products;


use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;

class Product extends Model
{
    /**
     * Create unique cache key
     *
     * @param string $slug
     * @param array $relations
     * @return string
     */
    public function cacheKey($slug = "", $relations = [])
    {
        return sprintf(
            "%s-%s-%s",
            $this->getTable(),
            $slug,
            implode("-",$relations)
        );
    }


    /**
     * Cache Product of slug
     * 
     * @param $slug
     * @param array $relations
     * @return mixed
     */
    public function getCachedProductOfSlug($slug, $relations = [])
    {
        return Cache::remember($this->cacheKey($slug, $relations) , 60, function ()  use($slug, $relations) {
            return $this->where('slug',$slug)->with($relations)->first();
        });
    }

    /**
     * Product of slug
     *
     * @param $slug
     * @param array $relations
     * @return mixed
     */
    public function productOfSlug($slug, $relations = [])
    {
        return $this->getCachedProductOfSlug($slug, $relations);
    }
}


Oct
09
2 months ago
Activity icon

Started a new Conversation Vue + Gridsome + Laravel 6

Anyone have experience or more importantly a boilerplate for Vue + Gridsome + Laravel. Looking for a React / Gatsby type solution for Laravel i.e. lazy loading images, load on scroll. Etc.

Any help would be greatly appreciated.

Sep
03
3 months ago
Activity icon

Replied to DateTime

@rider

There are a lot of ays to get it done, but assume it is in your model as a accessor (example assumes published at date)

    public function getPublishedAtAttribute($value)
    {
        return (new Carbon($value))->setTimezone('America/Denver')->-format(\DateTime::ISO8601);
    }

``
Activity icon

Replied to Illuminate \ Database \ QueryException (HY000) SQLSTATE[HY000]: General Error: 1364 Field 'p_id' Doesn't Have A Default Value (SQL: Insert Into `prod_cats` (`updated_at`, `created_at`) Values (2019-09-03 05:18:24, 2019-09-03 05:18:24))

@kanchan186

   
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\prod_cat;
use App\product;
use App\category;

class ProdCatController extends ProductController
{
   public function store1(Request $request)
    {

        if ($request->p_id && $request->p_id ) {

        if ($categor = prod_cat::create([
                'p_id'=>$request->p_id,
                'c_id'=>$request->c_id
               ]);) {

            // remove the rest for brevity sake 

            return response('Product category created successfully', 200);

        }

        // or whatever you want to do when it fails
        return response('Failed to find ids', 422);
    }
}


Activity icon

Replied to Countdown Timer

@matilarab here is a Vue countdown

  1. Load moment.js

  2. create Vue compoenent - countdown.vue


<template>
    <div class="countdown__container">
        <div v-if="finished" v-text="expiredText"></div>

        <div v-else class="countdown__date--container">
            <div class="countdown__item">
                <span class="countdown__heading">Days</span>
                <div class="countdown__date--item">
                    <span>{{ remaining.days }}</span>
                </div>
            </div>

            <div class="countdown__item">
                <span class="countdown__heading">Hours</span>
                <div class="countdown__date--item">
                    <span>{{ remaining.hours }}</span>
                </div>
            </div>
            <div class="countdown__item">
                <span class="countdown__heading">Seconds</span>
                <div class="countdown__date--item">
                    <span>{{ remaining.seconds }}</span>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    import moment from 'moment';

    export default {
        props: {
            until: String,
            expiredText: { default: 'Now Expired' }
        },
        data () {
            return { now: new Date() };
        },
        created () {
            this.refreshEverySecond();
        },
        computed: {
            finished () {
                return this.remaining.total <= 0;
            },
            remaining () {

                let remaining = moment.duration(Date.parse(this.until) - this.now);

                if (remaining <= 0) this.$emit('finished');

                return {
                    total: remaining,
                    years: this.pad(remaining.years(),2),
                    months: this.pad(remaining.months(),2),
                    days: this.pad(remaining.days(),2),
                    hours: this.pad(remaining.hours(),2),
                    minutes: this.pad(remaining.minutes(),2),
                    seconds: this.pad(remaining.seconds(),2)
                };
            }
        },
        methods: {

           pad(num, size) {
                var s = "000000000" + num;
                return s.substr(s.length-size);
            },
            refreshEverySecond () {
                let interval = setInterval(() => this.now = new Date(), 1000);
                this.$on('finished', () => clearInterval(interval));
            }
        }
    }
</script>

  1. SCSS - countdown.scss

NOTE: I have not made it responsive. Would need a little work for mobile.


.countdown__shadow {
  &:before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 40px;
    //height: 100%;
    //background: rgb(51,51,51);
    background: linear-gradient(180deg, rgba(51,51,51,0.6) 0%, rgba(255,255,255,0) 100%);
  }
}

.countdown__bg {
  background-color: #f1f1f1;
  box-shadow: inset 10px 0 20px rgba(0, 0, 0, 0.1),
    inset -10px 0 20px rgba(0, 0, 0, 0.1);
  height: auto;
  padding: 0 30px 60px 30px;
}

.countdown__container {
  display: flex;
  flex-direction: row;
  justify-content: center;
  width: 100%;

  //height: 400px;
  & .countdown__date--container {
    display: flex;
    flex-direction: row;
    justify-content: center;
    width: 100%;
    height: 150px;
    & .countdown__item {
      display: flex;
      flex-direction: column;
      justify-content: flex-start;
      padding: 5px;
      width: 10%;

      & .countdown__heading {
        width: 100%;
        margin-bottom: 5px;
        text-align: center;
        font: 300 1.6rem Raleway, sans-serif;
        text-transform: uppercase;
        letter-spacing: -0.5px;
        strong {
          font-weight: 400;
          color: #ea4c4c;
        }
      }

      & .countdown__date--item {
        position: relative;
        float: left;
        height: 110px;
        width: 100px;
        background-color: #fff;
        border-radius: 8px;
        box-shadow:0 3px 4px 0 rgba(0, 0, 0, .2),inset 2px 4px 0 0 rgba(255, 255, 255, .08);

        left: 0;
        right: 0;
        margin: auto;
        font: normal 5.94em/107px Oswald;
        font-weight: 700;
        color: #de4848;
        color: $color-orange-burnt;

        text-align: center;
      }
    }
  }
}

.countdown__heading-date {
  position: relative;
  display: block;
  width: 100%;
  margin: 0;
  line-height: 1rem;
  text-align: center;
  color: #555555;
  font: 100 2rem Oswald, sans-serif;
  text-transform: uppercase;
}

  1. in your HTML / Blade - set your date
                <countdown until="Dec 1, 2019"></countdown>

Activity icon

Replied to CSS For Backend Devs: Episode 7: Can't Get Tailwind Hooked Up

@oscarmoore normally that happens if you are runnng

Composer.json

{
    "private": true,
    "scripts": {
        "dev": "npm run development",
        "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
        "watch": "npm run development -- --watch",
        "watch-poll": "npm run watch -- --watch-poll",
        "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
        "prod": "npm run production",
        "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
    },
    "devDependencies": {
        "axios": "^0.19",
        "browser-sync": "^2.26.7",
        "browser-sync-webpack-plugin": "2.2.2",
        "cross-env": "^5.2",
        "laravel-mix": "^4.1.2",
        "laravel-mix-purgecss": "^4.1.0",
        "laravel-mix-tailwind": "^0.1.0",
        "lodash": "^4.17.15",
        "popper.js": "^1.15",
        "resolve-url-loader": "^3.1.0",
        "sass": "^1.22.10",
        "sass-loader": "^8.0.0",
        "tailwindcss": "^1.1.2",
        "vue": "^2.6.10",
        "vue-template-compiler": "^2.6.10",
        "clean-webpack-plugin": "^3.0.0",
        "purgecss-webpack-plugin": "^1.5.0",
        "purify-css": "^1.2.5",
        "purifycss-webpack": "^0.7.0",
        "webpack-bundle-analyzer": "^3.4.1",
        "webfontloader": "^1.6.28",
        "vue-loader": "^15.7.1"
    },
    "dependencies": {
        "moment": "^2.24.0",
    }
}

webpack.mix.js

NOTE: 1 ensure you have installed laravel-mix-tailwind i.e. npm install laravel-mix-tailwind --dev

  1. I dont use the sass loader / compiler but if you do then that would be in mix file
const mix = require('laravel-mix');

  require('laravel-mix-tailwind');
  require('laravel-mix-purgecss');

  /*
   |--------------------------------------------------------------------------
   | Mix Asset Management
   |--------------------------------------------------------------------------
   |
   | Mix provides a clean, fluent API for defining some Webpack build steps
   | for your Laravel application. By default, we are compiling the Sass
   | file for the application as well as bundling up all the JS files.
   |
   */


  mix.js('resources/js/app.js', 'public/js')
      .postCss('resources/css/app.css', 'public/css')
      .tailwind()
      .options({
          processCssUrls: false,
      })
      .purgeCss();

That you tailwind.js is in your root directory

module.exports = {
  theme: {
    extend: {}
  },
  variants: {},
  plugins: []
}

app.scss


@tailwind base;

@tailwind components;

@tailwind utilities;

Sep
02
3 months ago
Activity icon

Replied to Post Comment Form With Ajax

You are submitting the form using PHP versus AJAX / JQ ... so do something like this ...

FORM BLADE

  {!! Form::open(array('id'=>'myform')) !!}
    <div class="form-group" >
      {!! Form::label('comment',' ')  !!}
      {!! Form::textarea('comment',null,['placeholder'=>'Add a comment...','class' => 'form-control showbutton', 'rows'=>'1']) !!}    
    </div>
    {!! Form::hidden('id', $images->id) !!}
    <div class="form-group buttoncomment">
            <button class="send btn btn-danger" id="ajaxSubmit">Submit</button>
    </div>
  {!! Form::close() !!}


<script>
     $(document).ready(function(){
      $('#ajaxSubmit').click(function(e){
         e.preventDefault();
         $.ajaxSetup({
            headers: {
                'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content')
            }
        });
         $.ajax({
            url: "{{ url('/home/comment') }}",
            method: 'post',
            data: {
               name: $('#name').val(),
               type: $('#type').val(),
               price: $('#price').val()
            },
            success: function(result){
               console.log(result);
            }});
         });
      });
</script>

CONTROLLER

       public function store(Request $request)
       {
        if(Request::ajax()){
              $comment = new Comment();
              $comment->name = $request->name;
              $comment->type = $request->type;
              $comment->price = $request->price;

              $comment->save();
              return response()->json(['success'=>'Data is successfully added']);
        
    endif
       }

Activity icon

Replied to Jigsaw / Mix / Fontawesome

Fonts and webpack is a pain, but for SEO almost a must.

package.json

Install fortawesome, webfontloader and vue-fontawesome (Vue fontawesome component)


    {
      // <omitted for brevity sake>

      "devDependencies": {
       // <removed for brevity sake>

        "@fortawesome/fontawesome-free": "^5.8.1",
        "@fortawesome/fontawesome-svg-core": "^1.2.17",
        "@fortawesome/vue-fontawesome": "^0.1.6",
        "@fortawesome/free-brands-svg-icons": "^5.8.1",
        "@fortawesome/free-solid-svg-icons": "^5.8.1",
        "webfontloader": "^1.6.28"
      },
      "dependencies": {
      // <omitted for brevity sake>
      }
    }

wepack.mix.js

Install web font loader and require it in webpack.mix.js. I use google fonts example, but you can use any font. That said, I have not tested fontawesome.

    const mix = require('laravel-mix');
    const path = require('path');

    require('laravel-mix-tailwind');
    require('laravel-mix-purgecss');

    if (typeof window !== 'undefined') {
        var WebFont = require('webfontloader');

        WebFont.load({
            google: {
                families: [
                    'Lato:300,500',
                    'Roboto:300,500',
                    'Open+Sans:300,400,600,700',
                    'Montserrat:300,400,500'
                ],
            },
        });
    }


    mix.webpackConfig({
        module: {
            rules: [{
                test: /\.tsx?$/,
                loader: 'ts-loader',
                options: {
                    appendTsSuffixTo: [/\.vue$/],
                },
                exclude: /node_modules/,
            }],
        },
        entry: {
            vendor: [
                "webfontloader"
            ]
        },
        resolve: {
            extensions: ['*', '.js', '.jsx', '.vue', '.ts', '.tsx'],
            alias: {
                styles: path.resolve(__dirname, 'resources/assets/sass'),
                '@': path.resolve(__dirname, 'resources/assets/js'),
                webfontloader: path.resolve(__dirname, "./node_modules/webfontloader/webfontloader.js")
            },
        },
    })

app.js

I have been verbose to help illustrate the different loads from the multiple font-awesome packs, but you can load all and I believe use wildcards. Frankly, Im not a fan of the fontwesome approach and it is a pain in blade.

      … <removed for brevity sake>


    import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
    import { library } from '@fortawesome/fontawesome-svg-core'
    import { faCoffee,faSearch,faMinus,faTimes,faEllipsisV,faPhone,faEnvelope,faMapMarker,faEye,
        faFont,faAddressBook,faAddressCard,faHeart,faFilter,faWindowClose,faCheck,faChevronDown, faChevronRight,faChevronLeft,faTh,faList,faArrowAltCircleRight} from '@fortawesome/free-solid-svg-icons'
    import { faFacebook,faLinkedin,faInstagram,faTwitter,faYoutube,faGithub,faPinterest,faGoogle,faGooglePlus} from '@fortawesome/free-brands-svg-icons'
    library.add(faCoffee,faSearch,faMinus,faTimes,faEllipsisV,faPhone,faEnvelope,faMapMarker,faEye,faFont, faTh,faList,faAddressBook,
        faAddressCard,faHeart,faFilter,faWindowClose,faCheck,faChevronDown,faChevronRight,faChevronLeft,faArrowAltCircleRight);
    library.add(faFacebook,faLinkedin,faInstagram,faTwitter,faYoutube,faGithub,faPinterest,faGoogle,faGooglePlus);
    Vue.component('font-awesome-icon', FontAwesomeIcon);


      … <removed for brevity sake>

example.blade.php

an example element. I ran into problems where I had to be explicit and load the attribute as an array.

    <a itemprop="sameAs"
       title="CMO Solution Twitter"
       target="_blank"
       rel="nofollow"
       href="https://twitter.com/cmo_digital"
       class="linkable">
        <font-awesome-icon class="fab" :icon="['fab', 'twitter']"></font-awesome-icon>
    </a>


Hope that helps. It is a pain. Let me know if you have questions.