Plugin Directory

Changeset 2714132


Ignore:
Timestamp:
04/25/2022 03:50:06 AM (4 years ago)
Author:
bhartlenn
Message:

Switching from admin-ajax to Wordpress REST API for viewing full portfolio item, and for filtering portfolio items when clicking on a portfolio tag. Other minor bug fixes along the way.

Location:
bw-portfolio
Files:
4 deleted
9 edited
4 copied

Legend:

Unmodified
Added
Removed
  • bw-portfolio/tags/1.2.0/assets/css/bw-portfolio-style.css

    r2668990 r2714132  
    11/**
    2  * BW Portfolio Stylesheet
     2 * BW Portfolio Plugin Stylesheet
    33 */
    44
     
    1212
    1313section.portfolio_filter_tags {
    14   display: flex;
    15   margin: 0 0 1vh;
    16 }
    17 section.portfolio_filter_tags > a.portfolio_filter_tag {
    18   padding: 0.5em 1em;
    19   text-decoration: none;
    20   background: #efefef;
    21   border: 1px solid #ccc;
    22   border-radius: 50px;
    23   margin: 0 0.5vw 0 0;
    24   color: #444;
    25 }
    26 section.portfolio_filter_tags > a.portfolio_filter_tag:focus {
     14    display: flex;
     15    margin: 0.5em 0 1em;
     16}
     17
     18section.portfolio_filter_tags>a.portfolio_filter_tag {
     19    margin: 0 0.5vw 0 0;
     20    padding: 0.5em 1em;
     21    font-size: 0.8em;
     22    text-decoration: none;
     23    background: #efefef;
     24    border: 1px solid #ccc;
     25    border-radius: 5px;
     26    color: #444;
     27}
     28
     29section.portfolio_filter_tags>a.portfolio_filter_tag:focus {
    2730    border: none;
    2831    outline: none;
    29 }
    30 section.portfolio_filter_tags > a.portfolio_filter_tag.bwbh_active_tag {
     32}
     33section.portfolio_filter_tags>a.portfolio_filter_tag:hover {
     34    background: #fff;
     35}
     36
     37section.portfolio_filter_tags>a.portfolio_filter_tag.bwbh_active_tag,
     38section.portfolio_filter_tags>a.portfolio_filter_tag.bwbh_active_tag:link,
     39section.portfolio_filter_tags>a.portfolio_filter_tag.bwbh_active_tag:visited,
     40section.portfolio_filter_tags>a.portfolio_filter_tag.bwbh_active_tag:focus,
     41section.portfolio_filter_tags>a.portfolio_filter_tag.bwbh_active_tag:active {
    3142    background: #02b;
    3243    color: #fff;
    33    
    34 }
    35 
    36 .bw_portfolio_heading {
    37    
    38 }
    39 
    40 .bw_portfolio_content {
     44}
     45
     46.bw_portfolio_heading {}
     47
     48.bw_portfolio_content_area {
    4149    display: grid;
    4250    grid-gap: 1rem;
    4351    align-items: top;
    44     grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); /* fit as many 300px columns as possible in */
     52    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
     53    /* fit as many 300px columns as possible in */
    4554    grid-auto-rows: minmax(min-content, auto);
    4655}
     
    4958 * one column layout
    5059 */
    51 .bw_portfolio_content.col-1-layout {
    52     grid-template-columns: repeat(1, 1fr); /* repeat column once */
    53 }
     60.bw_portfolio_content_area.col-1-layout {
     61    grid-template-columns: repeat(1, 1fr);
     62    /* repeat column once */
     63}
     64
    5465/**
    5566 * two column layout
    5667 */
    57 .bw_portfolio_content.col-2-layout {
    58     grid-template-columns: repeat(2, 1fr); /* repeat twice */
    59 }
     68.bw_portfolio_content_area.col-2-layout {
     69    grid-template-columns: repeat(2, 1fr);
     70    /* repeat twice */
     71}
     72
    6073/**
    6174 * three column layout
    6275 */
    63 .bw_portfolio_content.col-3-layout {
    64     grid-template-columns: repeat(3, 1fr); /* repeat three times */
    65 }
     76.bw_portfolio_content_area.col-3-layout {
     77    grid-template-columns: repeat(3, 1fr);
     78    /* repeat three times */
     79}
     80
    6681/**
    6782 * four column layout
    6883 */
    69 .bw_portfolio_content.col-4-layout {
    70     grid-template-columns: repeat(4, 1fr); /* repeat four times */
     84.bw_portfolio_content_area.col-4-layout {
     85    grid-template-columns: repeat(4, 1fr);
     86    /* repeat four times */
    7187}
    7288
     
    86102}
    87103
    88 .bw_portfolio_content > .bw_portfolio_item > a,
    89 .bw_portfolio_content > .bw_portfolio_item > a:link,
    90 .bw_portfolio_content > .bw_portfolio_item > a:visited,
    91 .bw_portfolio_content > .bw_portfolio_item > a:focus,
    92 .bw_portfolio_content > .bw_portfolio_item > a:hover {
    93   text-decoration: none;
    94   color: initial;
     104.bw_portfolio_content_area>.bw_portfolio_item>a,
     105.bw_portfolio_content_area>.bw_portfolio_item>a:link,
     106.bw_portfolio_content_area>.bw_portfolio_item>a:visited,
     107.bw_portfolio_content_area>.bw_portfolio_item>a:focus,
     108.bw_portfolio_content_area>.bw_portfolio_item>a:hover {
     109    text-decoration: none;
     110    color: initial;
    95111}
    96112
     
    102118.bw_portfolio_item_text {
    103119    padding: 1em;
    104 }
    105 
    106 .bw_portfolio_item_title {
    107    
    108 }
    109 
    110 .bw_portfolio_item_content {
    111    
    112 }
     120}
     121
     122.bw_portfolio_item_title {}
     123
     124.bw_portfolio_item_content {}
    113125
    114126.bw_portfolio_tags {
     
    116128    font-weight: lighter;
    117129    font-style: italic;
    118 }
    119 
    120 .bw_portfolio_item > a, .bw_portfolio_item > a:link, .bw_portfolio_item > a:visited, .bw_portfolio_item > a:active {
     130    font-size: 0.8em;
     131}
     132
     133.bw_portfolio_tags span::after {
     134    content: " ";
     135}
     136
     137.bw_portfolio_item>a,
     138.bw_portfolio_item>a:link,
     139.bw_portfolio_item>a:visited,
     140.bw_portfolio_item>a:focus,
     141.bw_portfolio_item>a:active {
    121142    display: inline-block;
    122143    height: 100%;
    123144    text-decoration: none;
    124145}
     146
    125147.bw_portfolio_item a.bw_tag {
    126148    font-size: 0.9em;
     
    139161    justify-content: center;
    140162}
     163
    141164.bw_portfolio_modal {
    142165    display: none;
     
    153176}
    154177
    155 .bw_portfolio_container .loader, .bw_portfolio_modal .loader {
     178.bw_portfolio_container .loader,
     179.bw_portfolio_modal .loader {
     180    display: block;
    156181    position: relative;
    157182    max-width: 100px;
     
    161186
    162187.bw_portfolio_modal_close {
    163     position: absolute;
    164     top: 0;
    165     right: 0;
    166     display: block;
    167     padding: 0 0.25em;
     188    position: relative;
     189    top: -15px;
     190    right: -25px;
     191    padding: 0;
    168192    margin: 0;
    169     font-size: 2.5em;
    170193    text-decoration: none;
    171194    line-height: 1;
    172 }
     195    align-self: flex-end;
     196}
     197
    173198.bw_portfolio_item_full {
    174199    position: relative;
    175     display: block;
     200    display: flex;
     201    flex-direction: column;
    176202    margin: 0 auto;
    177203    padding: 2em;
     
    187213    box-shadow: 4px 4px 6px 0px #222;
    188214}
     215
    189216.bw_portfolio_item_full h3 {
    190217    margin: 2vh 0 0;
     
    194221 * Media Queries
    195222 */
    196  /* Extra small devices (phones, 600px and down) */
     223/* Extra small devices (phones, 600px and down) */
    197224@media only screen and (max-width: 600px) {
    198225    .bw_portfolio_item_full {
     
    209236
    210237/* Medium devices (landscape tablets, 768px and up) */
    211 @media only screen and (min-width: 768px) {
    212    
    213 }
     238@media only screen and (min-width: 768px) {}
    214239
    215240/* Large devices (laptops, 992px and up) */
    216 @media only screen and (min-width: 992px) {
    217    
    218 }
     241@media only screen and (min-width: 992px) {}
    219242
    220243/* Even Larger devices (laptops and desktops, 1200px and up) */
  • bw-portfolio/tags/1.2.0/assets/js/bw-portfolio-script.js

    r2668990 r2714132  
    22   
    33    if ( document.querySelector('.bw_portfolio_container') !== null ) {
     4       
     5        // helper function that closes/hides/resets the full portfolio item modal
     6        function closeModal(bwbh_modal_selector = bwbh_modal_el) {
     7            bwbh_modal_selector.style.display = "none";
     8            bwbh_modal_selector.innerHTML = "<img class='loader' src='" + bwbh_portfolio_js_vars.loader_gif_url + "'>";
     9        }
     10
     11        // create modal
     12        const bwbh_modal_el = document.createElement("div");
     13        bwbh_modal_el.classList.add("bw_portfolio_modal");
     14
     15        // add loader gif to modal
     16        const bwbh_loader_gif = document.createElement('img');
     17        bwbh_loader_gif.setAttribute('class', 'loader');
     18        bwbh_loader_gif.setAttribute('src', bwbh_portfolio_js_vars.loader_gif_url);
     19        bwbh_modal_el.appendChild(bwbh_loader_gif);
     20
     21       
     22        // append modal to body
     23        document.body.appendChild(bwbh_modal_el);
    424   
    5         // helper function that closes/hides/resets the full portfolio item modal
    6         function closeModal(bw_modal_selector = bw_modal_el) {
    7             bw_modal_selector.style.display = "none";
    8             bw_modal_selector.innerHTML = "<img class='loader' src='" + bw_portfolio_js_vars.loader_gif_url + "'>";
    9         }
    10    
    11         // flag var and function to prevent multiple clicks of relevant links
     25        // flag variable used to prevent multiple clicks of relevant links
    1226        let clicked;
    13    
     27       
     28        // detecting all clicks on document because dealing with dynamically added elements
    1429        document.addEventListener('click', function(e){
    1530       
    16             // prevents multiple clicks, set clicked to false in callback
     31            // try to prevent multiple clicks, set clicked to false at end of click functionality sections below
    1732            if (clicked) {
    1833                return false;
     
    2237            setTimeout(function(){
    2338                clicked = false;
    24             }, 2000);
    25        
    26             // if clicking on portfolio item, or anyting that has portfolio item as parent, and modal_off attribute is NOT set(adds .modal_off class to .bw_portfolio_content)
     39            }, 750);
     40       
     41
     42
     43            /**
     44             * if clicking on portfolio item, or anyting that has portfolio item as parent, and modal_off attribute is NOT set(adds .modal_off class to .bw_portfolio_content_area)
     45             */
    2746            if ( (e.target.classList.contains('bw_portfolio_item') || e.target.closest('.bw_portfolio_item') !== null) &&
    28             !e.target.closest('.bw_portfolio_content').classList.contains('modal_off') ) {
     47            !e.target.closest('.bw_portfolio_content_area').classList.contains('modal_off') ) {
    2948                e.preventDefault();
    3049           
     
    3756                }
    3857               
    39                 // boolean used to handle showing tags on cards of filtered portfolio items
    40                 let bwbh_show_tags = portfolio_item.closest('.bw_portfolio_content').classList.contains('show_tags');
     58                // boolean used to handle showing tags on cards of filtered and full modal view portfolio items
     59                let bwbh_show_tags = portfolio_item.closest('.bw_portfolio_content_area').classList.contains('show_tags');
    4160                       
    4261                // display modal on portfolio item click
    43                 bw_modal_el.style.display = "grid";
    44            
    45                 const bwbh_data = new FormData();
    46                 bwbh_data.append( 'action', 'bwbh_fetch_portfolio_item' );
    47                 bwbh_data.append( 'no_nonce_sense', bw_portfolio_js_vars.nonce );
    48                 bwbh_data.append( 'post_id', portfolio_item.dataset.post_id);
    49                 bwbh_data.append( 'bwbh_show_tags', bwbh_show_tags);
    50            
    51                 fetch(bw_portfolio_js_vars.ajax_url, {
    52                     method: "POST",
     62                bwbh_modal_el.style.display = "grid";
     63           
     64                const bwbh_form_data = new FormData();
     65                bwbh_form_data.append( 'bw_portfolio_item_id', portfolio_item.dataset.post_id);
     66                bwbh_form_data.append( 'bw_show_tags', bwbh_show_tags);
     67
     68                // format form data as plain, then into JSON so fetch can send data as JSON
     69                const form_data_json = JSON.stringify( Object.fromEntries( bwbh_form_data.entries() ) );
     70               
     71                fetch(wpApiSettings.root + 'bw-portfolio/v1/view-portfolio-item', {
     72                    method: 'POST',
    5373                    credentials: 'same-origin',
    54                     body: bwbh_data
     74                    headers: {
     75                        "Content-Type": "application/json",
     76                        "Accept": "application/json",
     77                        "X-WP-Nonce": bwbh_portfolio_js_vars.nonce,
     78                    },
     79                    body: form_data_json,
    5580                })
    56                 .then((response) => response.text())
    57                 .then((data) => {               
    58                     bw_modal_el.innerHTML = data;
     81                .then( response => response.json() )
     82                .then( data => {
     83                   
     84                    // remove loader gif from our modal
     85                    if( bwbh_modal_el.querySelector('.loader') !== null ) {
     86                        bwbh_modal_el.querySelector('.loader').remove();
     87                    }
     88                   
     89                    /**
     90                     * dynamically create full view of portfolio item and then insert into modal
     91                     */
     92                    const bwbh_portfolio_item_full = document.createElement("article");
     93                    bwbh_portfolio_item_full.setAttribute('class', 'bw_portfolio_item_full');
     94
     95                    // add close modal link
     96                    const bwbh_close_link = document.createElement('a');
     97                    bwbh_close_link.setAttribute('href', '');
     98                    bwbh_close_link.setAttribute('class', 'bw_portfolio_modal_close dashicons dashicons-dismiss');
     99                    bwbh_portfolio_item_full.appendChild(bwbh_close_link);
     100
     101                    // portfolio item featured image
     102                    const bwbh_feat_image = document.createElement('img');
     103                    bwbh_feat_image.classList.add('bw_portfolio_item_full_image');
     104                    bwbh_feat_image.setAttribute('src', data.feat_image_url);
     105                    bwbh_portfolio_item_full.appendChild(bwbh_feat_image);
     106                       
     107                    // add portfolio item text div for holding title, content, and tags
     108                    const bwbh_portfolio_text = document.createElement('div');
     109                    bwbh_portfolio_text.setAttribute('class', 'bw_portfolio_item_full_text');
     110
     111                    // add heading for title to text area
     112                    const bwbh_portfolio_heading = document.createElement('h3');
     113                    bwbh_portfolio_heading.setAttribute('class', 'bw_portfolio_item_full_title');
     114                    bwbh_portfolio_heading.textContent = data.title;
     115                    bwbh_portfolio_text.appendChild(bwbh_portfolio_heading);
     116
     117                    // add content div to text area
     118                    const bwbh_portfolio_item_content = document.createElement('div');
     119                    bwbh_portfolio_item_content.setAttribute('class', 'bw_portfolio_item_full_content');
     120                    bwbh_portfolio_item_content.innerHTML = data.content;
     121                    bwbh_portfolio_text.appendChild(bwbh_portfolio_item_content);
     122
     123                    // add portfolio item tags if shortcode attribute show_tags is present or true, and a tag(s) have been sent in json response
     124                    if( data.bw_show_tags === true && Object.keys(data.bw_portfolio_tags).length > 0 ) {
     125
     126                        const bwbh_portfolio_tags = document.createElement('section');
     127                        bwbh_portfolio_tags.setAttribute('class', 'bw_portfolio_tags');
     128                       
     129                        const bwbh_span = document.createElement('span');
     130                        bwbh_span.innerHTML = "Tags:";
     131                        bwbh_portfolio_tags.appendChild(bwbh_span);
     132
     133                        for (let tag in data.bw_portfolio_tags) {
     134                            const bwbh_portfolio_tag = document.createElement('span');
     135                            bwbh_portfolio_tag.innerHTML = data.bw_portfolio_tags[tag];
     136                            bwbh_portfolio_tags.append( bwbh_portfolio_tag );
     137                        }
     138                        bwbh_portfolio_text.appendChild(bwbh_portfolio_tags);
     139                    }
     140
     141                    // add text div with title, content, and tags to full portfolio item
     142                    bwbh_portfolio_item_full.appendChild(bwbh_portfolio_text);
     143
     144                    // add full portfolio item to modal
     145                    bwbh_modal_el.appendChild(bwbh_portfolio_item_full);
     146
     147                    // reset clicked flag variable
    59148                    clicked = false;
     149
    60150                })
    61151                .catch((error) => {
     
    65155            }
    66156       
    67             // if clicking on portfolio tag filter link, display portfolio items with that tag
     157
     158
     159            /**
     160             * if clicking on portfolio tag filter link, display portfolio items with that tag
     161             */
    68162            if (e.target.classList.contains('portfolio_filter_tag')) {
    69163                e.preventDefault();
     
    74168                let bwbh_filter_tags = bwbh_portfolio_container.querySelectorAll('.portfolio_filter_tag');
    75169                let bwbh_portfolio_container_height = bwbh_portfolio_container.clientHeight;
    76                 let bwbh_portfolio_content = bwbh_portfolio_container.querySelector('.bw_portfolio_content');
     170                let bwbh_portfolio_content_area = bwbh_portfolio_container.querySelector('.bw_portfolio_content_area');
    77171                let bwbh_term = bwbh_clicked_tag.dataset.term;
    78                 let bwbh_num_of_words = bwbh_portfolio_content.dataset.num_of_words;
     172                let bwbh_num_of_words = bwbh_portfolio_content_area.dataset.num_of_words;
    79173           
    80174                // remove .bwbh_active_tag class from any active filter tags
     
    91185           
    92186                // create loader gif element
    93                 let bwbh_loader_gif = document.createElement('img'); // = "<img class='loader' src='" + bw_portfolio_js_vars.loader_gif_url + "'>";
     187                let bwbh_loader_gif = document.createElement('img');
    94188                bwbh_loader_gif.classList.add('loader');
    95                 bwbh_loader_gif.setAttribute('src', bw_portfolio_js_vars.loader_gif_url);
     189                bwbh_loader_gif.setAttribute('src', bwbh_portfolio_js_vars.loader_gif_url);
    96190           
    97191                if (bwbh_portfolio_container !== null && bwbh_portfolio_container_height !== null) {
     
    101195                    bwbh_portfolio_container.style.height = bwbh_portfolio_container_height + "px";
    102196                }
    103            
     197               
    104198                // clear the old set of portfolio items
    105                 if (bwbh_portfolio_content !== null) {
    106                     bwbh_portfolio_content.innerHTML = "";
     199                if (bwbh_portfolio_content_area !== null) {
     200                    bwbh_portfolio_content_area.innerHTML = "";
    107201                }
    108202           
    109203                // boolean used to handle showing tags on cards of filtered portfolio items
    110                 let bwbh_show_tags = bwbh_portfolio_content.classList.contains('show_tags');
     204                let bwbh_show_tags = bwbh_portfolio_content_area.classList.contains('show_tags');
    111205                // boolean used to handle whether modal is on or off for filtered portfolio items
    112                 let bwbh_modal_off = bwbh_portfolio_content.classList.contains('modal_off');
    113            
     206                let bwbh_modal_off = bwbh_portfolio_content_area.classList.contains('modal_off');
     207               
     208                // setup form data to send with feth request
    114209                const bwbh_data1 = new FormData();
    115                 bwbh_data1.append( 'action', 'bwbh_filter_portfolio_items' );
    116                 bwbh_data1.append( 'no_nonce_sense', bw_portfolio_js_vars.nonce );
    117210                bwbh_data1.append( 'bwbh_term', bwbh_term);
    118211                bwbh_data1.append( 'bwbh_show_tags', bwbh_show_tags);
    119212                bwbh_data1.append( 'bwbh_modal_off', bwbh_modal_off);
    120213                bwbh_data1.append( 'bwbh_num_of_words', bwbh_num_of_words);
    121            
    122                 fetch(bw_portfolio_js_vars.ajax_url, {
    123                     method: "POST",
     214
     215                // format form data as plain, then into JSON so fetch can send data as JSON
     216                const form1_data_json = JSON.stringify( Object.fromEntries( bwbh_data1.entries() ) );
     217           
     218                /**
     219                 * Make fetch request
     220                 */
     221                fetch( wpApiSettings.root + 'bw-portfolio/v1/filter-portfolio-items', {
     222                    method: 'POST',
    124223                    credentials: 'same-origin',
    125                     body: bwbh_data1
     224                    headers: {
     225                        "Content-Type": "application/json",
     226                        "Accept": "application/json",
     227                        "X-WP-Nonce": bwbh_portfolio_js_vars.nonce,
     228                    },
     229                    body: form1_data_json
    126230                })
    127                 .then((response) => response.text())
    128                 .then((data) => {                       
     231                .then( response => response.json() )
     232                .then( data => {
     233                   
     234                    console.log(data);
     235
    129236                    bwbh_loader_gif.remove();
    130                     bwbh_portfolio_content.innerHTML = data;
    131                     bwbh_portfolio_container.style.height = "auto";
    132                     clicked = false;
     237                                       
     238                    for ( let bwbh_portfolio_item_id in data.portfolio_items ) {
     239                        /**
     240                         * dynamically create card view of each portfolio item and then insert into portfolio container content area .bw_portfolio_content_area
     241                         */
     242                        const bwbh_portfolio_item_card = document.createElement("article");
     243                        bwbh_portfolio_item_card.setAttribute('class', 'bw_portfolio_item');
     244                        bwbh_portfolio_item_card.setAttribute('data-post_id', bwbh_portfolio_item_id);
     245                        bwbh_portfolio_item_card.setAttribute('data-permalink', data.portfolio_items[bwbh_portfolio_item_id].permalink);
     246
     247                        // portfolio item featured image
     248                        const bwbh_feat_image = document.createElement('img');
     249                        bwbh_feat_image.classList.add('bw_portfolio_item_image');
     250                        bwbh_feat_image.setAttribute('src', data.portfolio_items[bwbh_portfolio_item_id].feat_image_url);                       
     251                       
     252                        // add portfolio item text div for holding title, content, and tags
     253                        const bwbh_portfolio_text = document.createElement('div');
     254                        bwbh_portfolio_text.setAttribute('class', 'bw_portfolio_item_text');
     255                       
     256                        // add heading for title
     257                        const bwbh_portfolio_heading = document.createElement('h3');
     258                        bwbh_portfolio_heading.setAttribute('class', 'bw_portfolio_item_title');
     259                        bwbh_portfolio_heading.innerHTML = data.portfolio_items[bwbh_portfolio_item_id].title;
     260                        bwbh_portfolio_text.appendChild(bwbh_portfolio_heading);
     261                       
     262                        // add content div
     263                        const bwbh_portfolio_content = document.createElement('div');
     264                        bwbh_portfolio_content.setAttribute('class', 'bw_portfolio_item_content');
     265                        bwbh_portfolio_content.innerHTML = data.portfolio_items[bwbh_portfolio_item_id].content;
     266                        bwbh_portfolio_text.appendChild(bwbh_portfolio_content);
     267
     268                       
     269                        // add portfolio item tags if shortcode attribute show_tags is present or true, and a tag(s) have been sent in json response
     270                        if( data.show_tags === true && Object.keys(data.portfolio_items[bwbh_portfolio_item_id].bw_portfolio_tags).length > 0 ) {
     271
     272                            const bwbh_portfolio_tags = document.createElement('section');
     273                            bwbh_portfolio_tags.setAttribute('class', 'bw_portfolio_tags');
     274                           
     275                            const bwbh_default_span = document.createElement('span');
     276                            bwbh_default_span.innerHTML = "Tags:";
     277                            bwbh_portfolio_tags.appendChild(bwbh_default_span);
     278
     279                            for (let tag in data.portfolio_items[bwbh_portfolio_item_id].bw_portfolio_tags) {
     280                                const bwbh_portfolio_tag = document.createElement('span');
     281                                bwbh_portfolio_tag.innerHTML = data.portfolio_items[bwbh_portfolio_item_id].bw_portfolio_tags[tag];
     282                                bwbh_portfolio_tags.append( bwbh_portfolio_tag );
     283                            }
     284                            bwbh_portfolio_text.appendChild(bwbh_portfolio_tags);
     285                        }
     286
     287                        // add text div with title, content, and portfolio feaured image to the portfolio item link, or to the portfolio item
     288                        if( data.modal_off === true ) {
     289                            const bwbh_regular_link = document.createElement('a');
     290                            bwbh_regular_link.setAttribute('href', data.portfolio_items[bwbh_portfolio_item_id].permalink);
     291                            bwbh_portfolio_item_card.appendChild(bwbh_regular_link);
     292                           
     293                            bwbh_regular_link.appendChild(bwbh_feat_image);
     294                            bwbh_regular_link.appendChild(bwbh_portfolio_text);
     295                        }
     296                        else {
     297                            bwbh_portfolio_item_card.appendChild(bwbh_feat_image);
     298                            bwbh_portfolio_item_card.appendChild(bwbh_portfolio_text);
     299                        }
     300                       
     301                       
     302                        // add portfolio item card to portfolio content area
     303                        bwbh_portfolio_content_area.appendChild(bwbh_portfolio_item_card);
     304                    }
     305
     306                     // reset clicked flag variable
     307                     clicked = false;
     308 
    133309                })
    134310                .catch((error) => {
     
    154330            }
    155331        });
    156    
    157         // create modal and append to body
    158         const bw_modal = document.createElement("div");
    159         bw_modal.classList.add("bw_portfolio_modal");
    160         bw_modal.innerHTML = "<img class='loader' src='" + bw_portfolio_js_vars.loader_gif_url + "'>";
    161         document.body.appendChild(bw_modal);
    162         // select and store modal for later use
    163         bw_modal_el = document.body.querySelector('.bw_portfolio_modal');
    164332       
    165333    }
  • bw-portfolio/tags/1.2.0/bw-portfolio.php

    r2668996 r2714132  
    33* Plugin Name: BW Portfolio
    44* Description: The BW Portfolio plugin is powerful yet lightweight and fast. It allows you to easily add portfolio items in your WordPress Dashboard, and organize them with portfolio tags as well. Then by using a handy shortcode you can display your portfolio items just about anywhere in a nice, responsive css grid that is compatible on many different devices. Has tag filtering of portfolio items built in.
    5 * Version: 1.1.7
     5* Version: 1.2.0
    66* Requires at least: 5.2
    77* Requires PHP: 7.0
     
    3131 * Enqueue Scripts and Stylesheets
    3232 */
    33 add_action( 'wp_enqueue_scripts', 'bwbh_portfolio_scripts_styles' );
    34 function bwbh_portfolio_scripts_styles() {
    35     wp_enqueue_style( 'bw-portfolio-style', plugins_url('/assets/css/bw-portfolio-style.css', __FILE__), [], filemtime( plugin_dir_path(__FILE__).'assets/css/bw-portfolio-style.css' ), 'screen' );
     33add_action( 'wp_enqueue_scripts', function() {
    3634   
    37     wp_enqueue_script( 'bw-portfolio-script', plugins_url('/assets/js/bw-portfolio-script.js', __FILE__), array(), '1.1.0', true );
    38     wp_localize_script( 'bw-portfolio-script', 'bw_portfolio_js_vars', [
    39         'ajax_url' => admin_url( 'admin-ajax.php' ),
    40         'nonce' => wp_create_nonce( 'bw_portfolio_secret_nonce_sauce' ),
    41         'loader_gif_url' => esc_url( plugins_url( 'bw-portfolio/assets/images/loading.gif', dirname(__FILE__) ) ),
    42     ]);
    43 }
    44 
    45 
    46 /**
    47  * handle fetch requests to load portfolio items content in modal
    48  */
    49 add_action('wp_ajax_bwbh_fetch_portfolio_item', 'bwbh_fetch_portfolio_item');
    50 add_action('wp_ajax_nopriv_bwbh_fetch_portfolio_item', 'bwbh_fetch_portfolio_item');
    51 function bwbh_fetch_portfolio_item() {
    52    
    53     check_ajax_referer( 'bw_portfolio_secret_nonce_sauce', 'no_nonce_sense' );
    54    
    55    
    56     if($_POST['post_id']) {
    57         $bw_post_id = (int)$_POST['post_id'];
    58         $bw_post = get_post($bw_post_id);
    59        
    60         // need to get the shortcode attribute settings that might affect full portfolio item view here
    61         $bwbh_show_tags = filter_var($_POST['bwbh_show_tags'], FILTER_VALIDATE_BOOLEAN);
    62        
    63         $bw_output = include plugin_dir_path( __FILE__ ) . 'inc/portfolio-item-full.php';
    64        
    65         echo wp_kses_post($bw_output);
     35    if( !is_admin() ) {
     36        wp_enqueue_style( 'bw-portfolio-style', plugins_url('/assets/css/bw-portfolio-style.css', __FILE__), ['dashicons'], filemtime( plugin_dir_path(__FILE__).'assets/css/bw-portfolio-style.css' ), 'screen' );
     37        wp_enqueue_script( 'bw-portfolio-script', plugins_url('/assets/js/bw-portfolio-script.js', __FILE__), ['wp-api-request'], '1.2.0', true );
     38
     39        wp_localize_script( 'bw-portfolio-script', 'bwbh_portfolio_js_vars', [
     40            'ajax_url' => admin_url( 'admin-ajax.php' ),
     41            'nonce' => wp_create_nonce( 'wp_rest' ),
     42            'loader_gif_url' => esc_url( plugins_url( 'bw-portfolio/assets/images/loading.gif', dirname(__FILE__) ) ),
     43        ]);
     44    }
     45   
     46});
     47
     48
     49
     50add_action('rest_api_init', function () {
     51
     52    // custom route to view full portfolio item
     53    register_rest_route('bw-portfolio/v1', '/view-portfolio-item', [
     54        'methods' => 'POST',
     55        'callback' => 'bw_view_portfolio_item',
     56        'permission_callback' => '__return_true',
     57    ]);
     58
     59    // custom route to filter portfolio items
     60    register_rest_route('bw-portfolio/v1', '/filter-portfolio-items', [
     61        'methods' => 'POST',
     62        'callback' => 'bw_filter_portfolio_items',
     63        'permission_callback' => '__return_true',
     64    ]);
     65});
     66
     67/**
     68 * handle fetch requests to load full view of portfolio item in modal via rest api
     69 */
     70function bw_view_portfolio_item( $request ) {   
     71    // if $request includes the portfolio item id...
     72    if( $request['bw_portfolio_item_id'] ) {
     73
     74        $bw_portfolio_item_id = (int)$request['bw_portfolio_item_id'];
     75        $bw_post = get_post($bw_portfolio_item_id, OBJECT, 'display');
     76       
     77        // if portfolio item was found...
     78        if($bw_post !== null) {
     79
     80            // store the needed post data to send back in rest response
     81            $bw_response_data = [
     82                'title' => esc_html__($bw_post->post_title),
     83                'content' => apply_filters('the_content', $bw_post->post_content ),
     84            ];
     85           
     86            // get and add tags to $bw_post_data
     87            if( $bw_show_tags !== false ) {
     88                $bw_portfolio_tags = get_the_terms( $bw_post->ID, 'portfolio_tag' );
     89                if( !is_wp_error($bw_portfolio_tags) && $bw_portfolio_tags !== false ) {
     90                    foreach( $bw_portfolio_tags as $bw_tag ) {
     91                        $bw_response_data['bw_portfolio_tags'][$bw_tag->term_id] = $bw_tag->name;
     92                    }
     93                }
     94            }
     95
     96            // add featured image url to array
     97            $post_thumbnail_id = get_post_thumbnail_id( $bw_post->ID );
     98            if(!empty($post_thumbnail_id)) {
     99                $feat_image_urls = wp_get_attachment_image_src( $post_thumbnail_id, 'full' );
     100                $bw_response_data['feat_image_url'] = $feat_image_urls[0];
     101            }
     102
     103            // store the shortcode attribute settings that affect full portfolio item view here
     104            $bw_show_tags = filter_var($request['bw_show_tags'], FILTER_VALIDATE_BOOLEAN);
     105            $bw_response_data['bw_show_tags'] = $bw_show_tags;
     106
     107            return new WP_REST_Response( $bw_response_data, 200 );
     108        }
     109        else {
     110            // Return a WP_Error because the request product was not found. In this case we return a 404 because the main resource was not found.
     111            return new WP_Error( 'rest_portfolio_item_invalid', esc_html__( 'The portfolio item does not exist.', 'bw_portfolio' ), array( 'status' => 404 ) );
     112        }
     113    }
     114}
     115
     116
     117/**
     118 * handle fetch requests to filter portfolio items by portfolio tag
     119 */
     120function bw_filter_portfolio_items( $request ) {
     121   
     122    if( $request['bwbh_term'] ) {
     123       
     124        $bwbh_term = $request['bwbh_term'];
     125       
     126        // need to get the shortcode attribute settings that might affect filtered results here
     127        $bwbh_show_tags = filter_var($request['bwbh_show_tags'], FILTER_VALIDATE_BOOLEAN);
     128        $bwbh_modal_off = filter_var($request['bwbh_modal_off'], FILTER_VALIDATE_BOOLEAN);
     129        $bwbh_num_of_words = (int)$request['bwbh_num_of_words'];
     130
     131        $bw_response_data = [
     132            'term' => $bwbh_term,
     133            'show_tags' => $bwbh_show_tags,
     134            'modal_off' => $bwbh_modal_off,
     135            'num_of_words' => $bwbh_num_of_words,
     136        ];
     137
     138        // include file that builds custom WP_Query for filtered portfolio items
     139        include plugin_dir_path( __FILE__ ) . 'inc/portfolio-query.php';
     140
     141        // loop through query results, and only add relevant pieces of portfolio item data to the $bw_response_data array
     142        if ( $bw_portfolio_query->have_posts() ) :
     143            while ( $bw_portfolio_query->have_posts() ) : $bw_portfolio_query->the_post();
     144                $bw_response_data['portfolio_items'][get_the_ID()] = [
     145                    'title' => get_the_title(),
     146                    'content' => wp_kses_post( bwbh_limit_words( strip_tags( get_the_content() ), $bwbh_num_of_words ) ) . '...',
     147                ];
     148               
     149                // if modal_off is true, then send permalink for opening portfolio item manually
     150                if( $bwbh_modal_off ) {
     151                    $bw_response_data['portfolio_items'][get_the_ID()]['permalink'] = get_permalink( get_the_ID() );
     152                }
     153
     154                // get and add portfolio item tags to $bw_response_data if show_tags shortcode attribute was true
     155                if( $bwbh_show_tags !== false ) {
     156                    $bw_portfolio_tags = get_the_terms( get_the_ID(), 'portfolio_tag' );
     157                    if( !is_wp_error($bw_portfolio_tags) && $bw_portfolio_tags !== false ) {
     158                        foreach($bw_portfolio_tags as $bw_tag) {
     159                            $bw_response_data['portfolio_items'][get_the_ID()]['bw_portfolio_tags'][$bw_tag->term_id] = $bw_tag->name;
     160                        }
     161                    }
     162                }
     163
     164                // add featured image url to array if it exists
     165                $post_thumbnail_id = get_post_thumbnail_id( $bw_post->ID );
     166                if(!empty($post_thumbnail_id)) {
     167                    $feat_image_urls = wp_get_attachment_image_src( $post_thumbnail_id, 'full' );
     168                    $bw_response_data['portfolio_items'][get_the_ID()]['feat_image_url'] = $feat_image_urls[0];
     169                }
     170               
     171
     172            endwhile;
     173           
     174            wp_reset_postdata();
     175
     176        endif; // else if no portfolio items in tag are found then return error?(don't think this can happen though?)
     177
     178        return new WP_REST_Response( $bw_response_data, 200 );
     179       
    66180    }
    67181       
     
    69183}
    70184
    71 
    72 /**
    73  * handle fetch requests to filter portfolio items by portfolio tag
    74  */
    75 add_action('wp_ajax_bwbh_filter_portfolio_items', 'bwbh_filter_portfolio_items');
    76 add_action('wp_ajax_nopriv_bwbh_filter_portfolio_items', 'bwbh_filter_portfolio_items');
    77 function bwbh_filter_portfolio_items() {
    78    
    79     check_ajax_referer( 'bw_portfolio_secret_nonce_sauce', 'no_nonce_sense' );
    80    
    81     if($_POST['bwbh_term']) {
    82        
    83         $bwbh_term = $_POST['bwbh_term'];
    84        
    85         // need to get the shortcode attribute settings that might affect filtered results here
    86         $bwbh_show_tags = filter_var($_POST['bwbh_show_tags'], FILTER_VALIDATE_BOOLEAN);
    87         $bwbh_modal_off = filter_var($_POST['bwbh_modal_off'], FILTER_VALIDATE_BOOLEAN);
    88         $bwbh_num_of_words = (int)$_POST['bwbh_num_of_words'];
    89        
    90         // include file that builds custom WP_Query for filtered portfolio items
    91         include plugin_dir_path( __FILE__ ) . 'inc/portfolio-query.php';
    92        
    93         include plugin_dir_path( __FILE__ ) . 'inc/portfolio-filtered-loop.php';
    94        
    95     }
    96        
    97     wp_die();
    98 }
    99 
    100185/**
    101186* Register bw_portfolio post type
    102187* Register portfolio_tag taxonomy
    103188*/
    104 add_action( 'init', 'bwbh_portfolio_cpt_init' );
    105 function bwbh_portfolio_cpt_init() {
     189add_action( 'init', function() {
    106190    //
    107191    register_post_type('bw_portfolio',
     
    151235    unset( $bw_tax_labels );
    152236    unset( $bw_tax_args );
    153 }
    154 
    155 /**
    156 * Helper functions for the [bw_portfolio] Shortcode
    157 */
     237});
     238
     239/**
     240* Helper functions for the [bw_portfolio] Shortcode attributes
     241*/
     242
     243// num_of_words_on_cards shortcode attribute
    158244if (!function_exists('bwbh_limit_words')) {
    159245    function bwbh_limit_words($string, $word_limit) {
    160246        $words = explode(" ", trim($string));
    161         return implode(" ",array_splice($words, 0, $word_limit));
     247        return implode(" ", array_splice($words, 0, $word_limit));
    162248    }
    163249}
    164250
     251// shortcode attributes with no value set are normally sent through as "indexed key => attribute name" pairs, where normal shortcode attributes with a value set are sent as "attribute name => att value"
     252// a number of the shortcode attributes I have added are simply flag variables, so to make it easier for users they can simply enter the attribute name, instead of attribute name=true
    165253if (!function_exists('bwbh_normalize_empty_atts')) {
    166254    function bwbh_normalize_empty_atts($atts) {
    167255        foreach ($atts as $attribute => $value) {
    168             // empty value atts are sent through as "indexed key => attribute name" pairs, where normal atts with value set are sent as "attribute name => att value"
    169             // so, if key is an integer then that was an attribute set with no value, and we want to unset that, and add attr name into the atts array with value true
     256            // if $attribute/key is an integer then that was an attribute set with no value, and we want to unset that, and re-add the attr name into the $atts array with boolean value true
    170257            if (is_int($attribute)) {
    171258                $atts[strtolower($value)] = true;
  • bw-portfolio/tags/1.2.0/inc/portfolio-loop.php

    r2668996 r2714132  
    66if ( $bw_portfolio_query->have_posts() ) :
    77
    8     $portfolio_output .= "<section class='bw_portfolio_container'>\n";
     8    $portfolio_output .= "<section class='bw_portfolio_container alignwide'>\n";
    99
    1010    $portfolio_output .= "<header class='bw_portfolio_header'>\n";
     
    1313        $portfolio_output .= "<h2 class='bw_portfolio_heading'>" .  esc_html__( $bw_atts['portfolio_title'] ) . "</h2>\n";
    1414    }
    15     // portfolio tag filter links
     15    /**
     16     * portfolio tag filter links
     17     */
    1618    $bw_portfolio_tags = get_terms([
    1719        'taxonomy' => 'portfolio_tag',
    18         //'hide_empty' => true, // default is already true
     20        'orderby' => 'id'
    1921    ]);
    2022    if( !is_wp_error($bw_portfolio_tags) && count($bw_portfolio_tags) > 0 ) {
    2123       
    2224        $portfolio_output .= "<section class='portfolio_filter_tags'>\n";
    23        
    24         $portfolio_output .= "<a href='#' class='portfolio_filter_tag' data-term='show_all'>Show All</a>\n";
    25        
     25               
     26        $portfolio_output .= "<a class='portfolio_filter_tag' data-term='show_all' title='Show all portfolio items'>Show All</a>\n";
     27
    2628        foreach($bw_portfolio_tags as $bw_tag) {
    27             $portfolio_output .= "<a href='#' class='portfolio_filter_tag' data-term='" . $bw_tag->slug . "'>" . $bw_tag->name . "</a>\n";
     29            if( isset($bw_tags_array) ) {
     30                if( in_array($bw_tag->slug, $bw_tags_array) ) {
     31                    $portfolio_output .= "<a class='portfolio_filter_tag' data-term='" . $bw_tag->slug . "' title='Show portfolio items tagged with " . $bw_tag->name . "'>" . $bw_tag->name . "</a>\n";
     32                }
     33            }
     34            else {
     35                $portfolio_output .= "<a class='portfolio_filter_tag' data-term='" . $bw_tag->slug . "' title='Show portfolio items tagged with " . $bw_tag->name . "'>" . $bw_tag->name . "</a>\n";
     36            }
     37           
    2838        }
    2939       
     
    7686    $data_num_of_words = "data-num_of_words='" . $num_of_words . "'";
    7787   
    78     $portfolio_output .= "<div class='bw_portfolio_content" . $bw_content_classes . "' " . $data_num_of_words . ">\n";
     88    $portfolio_output .= "<div class='bw_portfolio_content_area" . $bw_content_classes . "' " . $data_num_of_words . ">\n";
    7989
    8090    while ( $bw_portfolio_query->have_posts() ) : $bw_portfolio_query->the_post();
    8191        // add portfolio item html to output variable
    82         $portfolio_output .= "<article class='bw_portfolio_item' data-post_id='" . get_the_ID() . "'>\n";
     92        $portfolio_output .= "<article class='bw_portfolio_item' data-post_id='" . get_the_ID() . "' data-permalink='" . get_the_permalink() . "' >\n";
    8393       
    8494        // if user adds modal_off attribute, then add html anchor link to handle normal anchor link click to the full portfolio item being displayed by theme template
     
    8797        }
    8898       
    89         $portfolio_output .= "<img src='" . esc_url( get_the_post_thumbnail_url(get_the_ID(), 'post-thumbnail') ) . "' class='bw_portfolio_item_image'>";
     99        $portfolio_output .= "<img src='" . esc_url( get_the_post_thumbnail_url(get_the_ID(), 'large') ) . "' class='bw_portfolio_item_image'>";
    90100       
    91101        $portfolio_output .= "<div class='bw_portfolio_item_text'>\n";
  • bw-portfolio/tags/1.2.0/inc/portfolio-query.php

    r2668990 r2714132  
    1616    $bw_tags_array = explode(',', $bw_tags_str);
    1717    // for each array value make spaces between words into dashes, and make words lowercase
    18     array_map('sanitize_title_with_dashes', $bw_tags_array);
     18    $bw_tags_array = array_map('sanitize_title_with_dashes', $bw_tags_array);
    1919    // trim whitespace off ends of tags
    20     array_map('trim', $bw_tags_array);
     20    $bw_tags_array = array_map('trim', $bw_tags_array);
    2121   
    2222    // if user clicked on portfolio tag filter link, empty array of filter terms from shortcode, and insert only the term user clicked on
    2323    if(!empty($bwbh_term)) {
    2424        $bw_tags_array = [];
    25         $bw_tags_array[] = strip_tags($bwbh_term);
     25        $bw_tags_array[] = strip_tags(trim($bwbh_term));
    2626    }
    2727   
  • bw-portfolio/tags/1.2.0/readme.txt

    r2668996 r2714132  
    66Tested up to: 5.9
    77Requires PHP: 7.0
    8 Stable tag: 1.1.7
     8Stable tag: 1.2.0
    99License: GPLv3
    1010License URI: https://www.gnu.org/licenses/gpl-3.0.html
     
    3333
    3434== Changelog ==
     35
     36= 1.2.0 =
     37* Major update where I stopped using admin-ajax and switched over to using the WordPress REST API for viewing a portfolio item, and filtering portfolio items by portfolio tag
     38* added .alignwide class to portfolio container so wordpress themes(like twentytwentone, etc) default styling allows it to be full width of its parent
     39* fixed some minor display bugs
    3540
    3641= 1.1.7 =
  • bw-portfolio/trunk/assets/css/bw-portfolio-style.css

    r2668990 r2714132  
    11/**
    2  * BW Portfolio Stylesheet
     2 * BW Portfolio Plugin Stylesheet
    33 */
    44
     
    1212
    1313section.portfolio_filter_tags {
    14   display: flex;
    15   margin: 0 0 1vh;
    16 }
    17 section.portfolio_filter_tags > a.portfolio_filter_tag {
    18   padding: 0.5em 1em;
    19   text-decoration: none;
    20   background: #efefef;
    21   border: 1px solid #ccc;
    22   border-radius: 50px;
    23   margin: 0 0.5vw 0 0;
    24   color: #444;
    25 }
    26 section.portfolio_filter_tags > a.portfolio_filter_tag:focus {
     14    display: flex;
     15    margin: 0.5em 0 1em;
     16}
     17
     18section.portfolio_filter_tags>a.portfolio_filter_tag {
     19    margin: 0 0.5vw 0 0;
     20    padding: 0.5em 1em;
     21    font-size: 0.8em;
     22    text-decoration: none;
     23    background: #efefef;
     24    border: 1px solid #ccc;
     25    border-radius: 5px;
     26    color: #444;
     27}
     28
     29section.portfolio_filter_tags>a.portfolio_filter_tag:focus {
    2730    border: none;
    2831    outline: none;
    29 }
    30 section.portfolio_filter_tags > a.portfolio_filter_tag.bwbh_active_tag {
     32}
     33section.portfolio_filter_tags>a.portfolio_filter_tag:hover {
     34    background: #fff;
     35}
     36
     37section.portfolio_filter_tags>a.portfolio_filter_tag.bwbh_active_tag,
     38section.portfolio_filter_tags>a.portfolio_filter_tag.bwbh_active_tag:link,
     39section.portfolio_filter_tags>a.portfolio_filter_tag.bwbh_active_tag:visited,
     40section.portfolio_filter_tags>a.portfolio_filter_tag.bwbh_active_tag:focus,
     41section.portfolio_filter_tags>a.portfolio_filter_tag.bwbh_active_tag:active {
    3142    background: #02b;
    3243    color: #fff;
    33    
    34 }
    35 
    36 .bw_portfolio_heading {
    37    
    38 }
    39 
    40 .bw_portfolio_content {
     44}
     45
     46.bw_portfolio_heading {}
     47
     48.bw_portfolio_content_area {
    4149    display: grid;
    4250    grid-gap: 1rem;
    4351    align-items: top;
    44     grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); /* fit as many 300px columns as possible in */
     52    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
     53    /* fit as many 300px columns as possible in */
    4554    grid-auto-rows: minmax(min-content, auto);
    4655}
     
    4958 * one column layout
    5059 */
    51 .bw_portfolio_content.col-1-layout {
    52     grid-template-columns: repeat(1, 1fr); /* repeat column once */
    53 }
     60.bw_portfolio_content_area.col-1-layout {
     61    grid-template-columns: repeat(1, 1fr);
     62    /* repeat column once */
     63}
     64
    5465/**
    5566 * two column layout
    5667 */
    57 .bw_portfolio_content.col-2-layout {
    58     grid-template-columns: repeat(2, 1fr); /* repeat twice */
    59 }
     68.bw_portfolio_content_area.col-2-layout {
     69    grid-template-columns: repeat(2, 1fr);
     70    /* repeat twice */
     71}
     72
    6073/**
    6174 * three column layout
    6275 */
    63 .bw_portfolio_content.col-3-layout {
    64     grid-template-columns: repeat(3, 1fr); /* repeat three times */
    65 }
     76.bw_portfolio_content_area.col-3-layout {
     77    grid-template-columns: repeat(3, 1fr);
     78    /* repeat three times */
     79}
     80
    6681/**
    6782 * four column layout
    6883 */
    69 .bw_portfolio_content.col-4-layout {
    70     grid-template-columns: repeat(4, 1fr); /* repeat four times */
     84.bw_portfolio_content_area.col-4-layout {
     85    grid-template-columns: repeat(4, 1fr);
     86    /* repeat four times */
    7187}
    7288
     
    86102}
    87103
    88 .bw_portfolio_content > .bw_portfolio_item > a,
    89 .bw_portfolio_content > .bw_portfolio_item > a:link,
    90 .bw_portfolio_content > .bw_portfolio_item > a:visited,
    91 .bw_portfolio_content > .bw_portfolio_item > a:focus,
    92 .bw_portfolio_content > .bw_portfolio_item > a:hover {
    93   text-decoration: none;
    94   color: initial;
     104.bw_portfolio_content_area>.bw_portfolio_item>a,
     105.bw_portfolio_content_area>.bw_portfolio_item>a:link,
     106.bw_portfolio_content_area>.bw_portfolio_item>a:visited,
     107.bw_portfolio_content_area>.bw_portfolio_item>a:focus,
     108.bw_portfolio_content_area>.bw_portfolio_item>a:hover {
     109    text-decoration: none;
     110    color: initial;
    95111}
    96112
     
    102118.bw_portfolio_item_text {
    103119    padding: 1em;
    104 }
    105 
    106 .bw_portfolio_item_title {
    107    
    108 }
    109 
    110 .bw_portfolio_item_content {
    111    
    112 }
     120}
     121
     122.bw_portfolio_item_title {}
     123
     124.bw_portfolio_item_content {}
    113125
    114126.bw_portfolio_tags {
     
    116128    font-weight: lighter;
    117129    font-style: italic;
    118 }
    119 
    120 .bw_portfolio_item > a, .bw_portfolio_item > a:link, .bw_portfolio_item > a:visited, .bw_portfolio_item > a:active {
     130    font-size: 0.8em;
     131}
     132
     133.bw_portfolio_tags span::after {
     134    content: " ";
     135}
     136
     137.bw_portfolio_item>a,
     138.bw_portfolio_item>a:link,
     139.bw_portfolio_item>a:visited,
     140.bw_portfolio_item>a:focus,
     141.bw_portfolio_item>a:active {
    121142    display: inline-block;
    122143    height: 100%;
    123144    text-decoration: none;
    124145}
     146
    125147.bw_portfolio_item a.bw_tag {
    126148    font-size: 0.9em;
     
    139161    justify-content: center;
    140162}
     163
    141164.bw_portfolio_modal {
    142165    display: none;
     
    153176}
    154177
    155 .bw_portfolio_container .loader, .bw_portfolio_modal .loader {
     178.bw_portfolio_container .loader,
     179.bw_portfolio_modal .loader {
     180    display: block;
    156181    position: relative;
    157182    max-width: 100px;
     
    161186
    162187.bw_portfolio_modal_close {
    163     position: absolute;
    164     top: 0;
    165     right: 0;
    166     display: block;
    167     padding: 0 0.25em;
     188    position: relative;
     189    top: -15px;
     190    right: -25px;
     191    padding: 0;
    168192    margin: 0;
    169     font-size: 2.5em;
    170193    text-decoration: none;
    171194    line-height: 1;
    172 }
     195    align-self: flex-end;
     196}
     197
    173198.bw_portfolio_item_full {
    174199    position: relative;
    175     display: block;
     200    display: flex;
     201    flex-direction: column;
    176202    margin: 0 auto;
    177203    padding: 2em;
     
    187213    box-shadow: 4px 4px 6px 0px #222;
    188214}
     215
    189216.bw_portfolio_item_full h3 {
    190217    margin: 2vh 0 0;
     
    194221 * Media Queries
    195222 */
    196  /* Extra small devices (phones, 600px and down) */
     223/* Extra small devices (phones, 600px and down) */
    197224@media only screen and (max-width: 600px) {
    198225    .bw_portfolio_item_full {
     
    209236
    210237/* Medium devices (landscape tablets, 768px and up) */
    211 @media only screen and (min-width: 768px) {
    212    
    213 }
     238@media only screen and (min-width: 768px) {}
    214239
    215240/* Large devices (laptops, 992px and up) */
    216 @media only screen and (min-width: 992px) {
    217    
    218 }
     241@media only screen and (min-width: 992px) {}
    219242
    220243/* Even Larger devices (laptops and desktops, 1200px and up) */
  • bw-portfolio/trunk/assets/js/bw-portfolio-script.js

    r2668990 r2714132  
    22   
    33    if ( document.querySelector('.bw_portfolio_container') !== null ) {
     4       
     5        // helper function that closes/hides/resets the full portfolio item modal
     6        function closeModal(bwbh_modal_selector = bwbh_modal_el) {
     7            bwbh_modal_selector.style.display = "none";
     8            bwbh_modal_selector.innerHTML = "<img class='loader' src='" + bwbh_portfolio_js_vars.loader_gif_url + "'>";
     9        }
     10
     11        // create modal
     12        const bwbh_modal_el = document.createElement("div");
     13        bwbh_modal_el.classList.add("bw_portfolio_modal");
     14
     15        // add loader gif to modal
     16        const bwbh_loader_gif = document.createElement('img');
     17        bwbh_loader_gif.setAttribute('class', 'loader');
     18        bwbh_loader_gif.setAttribute('src', bwbh_portfolio_js_vars.loader_gif_url);
     19        bwbh_modal_el.appendChild(bwbh_loader_gif);
     20
     21       
     22        // append modal to body
     23        document.body.appendChild(bwbh_modal_el);
    424   
    5         // helper function that closes/hides/resets the full portfolio item modal
    6         function closeModal(bw_modal_selector = bw_modal_el) {
    7             bw_modal_selector.style.display = "none";
    8             bw_modal_selector.innerHTML = "<img class='loader' src='" + bw_portfolio_js_vars.loader_gif_url + "'>";
    9         }
    10    
    11         // flag var and function to prevent multiple clicks of relevant links
     25        // flag variable used to prevent multiple clicks of relevant links
    1226        let clicked;
    13    
     27       
     28        // detecting all clicks on document because dealing with dynamically added elements
    1429        document.addEventListener('click', function(e){
    1530       
    16             // prevents multiple clicks, set clicked to false in callback
     31            // try to prevent multiple clicks, set clicked to false at end of click functionality sections below
    1732            if (clicked) {
    1833                return false;
     
    2237            setTimeout(function(){
    2338                clicked = false;
    24             }, 2000);
    25        
    26             // if clicking on portfolio item, or anyting that has portfolio item as parent, and modal_off attribute is NOT set(adds .modal_off class to .bw_portfolio_content)
     39            }, 750);
     40       
     41
     42
     43            /**
     44             * if clicking on portfolio item, or anyting that has portfolio item as parent, and modal_off attribute is NOT set(adds .modal_off class to .bw_portfolio_content_area)
     45             */
    2746            if ( (e.target.classList.contains('bw_portfolio_item') || e.target.closest('.bw_portfolio_item') !== null) &&
    28             !e.target.closest('.bw_portfolio_content').classList.contains('modal_off') ) {
     47            !e.target.closest('.bw_portfolio_content_area').classList.contains('modal_off') ) {
    2948                e.preventDefault();
    3049           
     
    3756                }
    3857               
    39                 // boolean used to handle showing tags on cards of filtered portfolio items
    40                 let bwbh_show_tags = portfolio_item.closest('.bw_portfolio_content').classList.contains('show_tags');
     58                // boolean used to handle showing tags on cards of filtered and full modal view portfolio items
     59                let bwbh_show_tags = portfolio_item.closest('.bw_portfolio_content_area').classList.contains('show_tags');
    4160                       
    4261                // display modal on portfolio item click
    43                 bw_modal_el.style.display = "grid";
    44            
    45                 const bwbh_data = new FormData();
    46                 bwbh_data.append( 'action', 'bwbh_fetch_portfolio_item' );
    47                 bwbh_data.append( 'no_nonce_sense', bw_portfolio_js_vars.nonce );
    48                 bwbh_data.append( 'post_id', portfolio_item.dataset.post_id);
    49                 bwbh_data.append( 'bwbh_show_tags', bwbh_show_tags);
    50            
    51                 fetch(bw_portfolio_js_vars.ajax_url, {
    52                     method: "POST",
     62                bwbh_modal_el.style.display = "grid";
     63           
     64                const bwbh_form_data = new FormData();
     65                bwbh_form_data.append( 'bw_portfolio_item_id', portfolio_item.dataset.post_id);
     66                bwbh_form_data.append( 'bw_show_tags', bwbh_show_tags);
     67
     68                // format form data as plain, then into JSON so fetch can send data as JSON
     69                const form_data_json = JSON.stringify( Object.fromEntries( bwbh_form_data.entries() ) );
     70               
     71                fetch(wpApiSettings.root + 'bw-portfolio/v1/view-portfolio-item', {
     72                    method: 'POST',
    5373                    credentials: 'same-origin',
    54                     body: bwbh_data
     74                    headers: {
     75                        "Content-Type": "application/json",
     76                        "Accept": "application/json",
     77                        "X-WP-Nonce": bwbh_portfolio_js_vars.nonce,
     78                    },
     79                    body: form_data_json,
    5580                })
    56                 .then((response) => response.text())
    57                 .then((data) => {               
    58                     bw_modal_el.innerHTML = data;
     81                .then( response => response.json() )
     82                .then( data => {
     83                   
     84                    // remove loader gif from our modal
     85                    if( bwbh_modal_el.querySelector('.loader') !== null ) {
     86                        bwbh_modal_el.querySelector('.loader').remove();
     87                    }
     88                   
     89                    /**
     90                     * dynamically create full view of portfolio item and then insert into modal
     91                     */
     92                    const bwbh_portfolio_item_full = document.createElement("article");
     93                    bwbh_portfolio_item_full.setAttribute('class', 'bw_portfolio_item_full');
     94
     95                    // add close modal link
     96                    const bwbh_close_link = document.createElement('a');
     97                    bwbh_close_link.setAttribute('href', '');
     98                    bwbh_close_link.setAttribute('class', 'bw_portfolio_modal_close dashicons dashicons-dismiss');
     99                    bwbh_portfolio_item_full.appendChild(bwbh_close_link);
     100
     101                    // portfolio item featured image
     102                    const bwbh_feat_image = document.createElement('img');
     103                    bwbh_feat_image.classList.add('bw_portfolio_item_full_image');
     104                    bwbh_feat_image.setAttribute('src', data.feat_image_url);
     105                    bwbh_portfolio_item_full.appendChild(bwbh_feat_image);
     106                       
     107                    // add portfolio item text div for holding title, content, and tags
     108                    const bwbh_portfolio_text = document.createElement('div');
     109                    bwbh_portfolio_text.setAttribute('class', 'bw_portfolio_item_full_text');
     110
     111                    // add heading for title to text area
     112                    const bwbh_portfolio_heading = document.createElement('h3');
     113                    bwbh_portfolio_heading.setAttribute('class', 'bw_portfolio_item_full_title');
     114                    bwbh_portfolio_heading.textContent = data.title;
     115                    bwbh_portfolio_text.appendChild(bwbh_portfolio_heading);
     116
     117                    // add content div to text area
     118                    const bwbh_portfolio_item_content = document.createElement('div');
     119                    bwbh_portfolio_item_content.setAttribute('class', 'bw_portfolio_item_full_content');
     120                    bwbh_portfolio_item_content.innerHTML = data.content;
     121                    bwbh_portfolio_text.appendChild(bwbh_portfolio_item_content);
     122
     123                    // add portfolio item tags if shortcode attribute show_tags is present or true, and a tag(s) have been sent in json response
     124                    if( data.bw_show_tags === true && Object.keys(data.bw_portfolio_tags).length > 0 ) {
     125
     126                        const bwbh_portfolio_tags = document.createElement('section');
     127                        bwbh_portfolio_tags.setAttribute('class', 'bw_portfolio_tags');
     128                       
     129                        const bwbh_span = document.createElement('span');
     130                        bwbh_span.innerHTML = "Tags:";
     131                        bwbh_portfolio_tags.appendChild(bwbh_span);
     132
     133                        for (let tag in data.bw_portfolio_tags) {
     134                            const bwbh_portfolio_tag = document.createElement('span');
     135                            bwbh_portfolio_tag.innerHTML = data.bw_portfolio_tags[tag];
     136                            bwbh_portfolio_tags.append( bwbh_portfolio_tag );
     137                        }
     138                        bwbh_portfolio_text.appendChild(bwbh_portfolio_tags);
     139                    }
     140
     141                    // add text div with title, content, and tags to full portfolio item
     142                    bwbh_portfolio_item_full.appendChild(bwbh_portfolio_text);
     143
     144                    // add full portfolio item to modal
     145                    bwbh_modal_el.appendChild(bwbh_portfolio_item_full);
     146
     147                    // reset clicked flag variable
    59148                    clicked = false;
     149
    60150                })
    61151                .catch((error) => {
     
    65155            }
    66156       
    67             // if clicking on portfolio tag filter link, display portfolio items with that tag
     157
     158
     159            /**
     160             * if clicking on portfolio tag filter link, display portfolio items with that tag
     161             */
    68162            if (e.target.classList.contains('portfolio_filter_tag')) {
    69163                e.preventDefault();
     
    74168                let bwbh_filter_tags = bwbh_portfolio_container.querySelectorAll('.portfolio_filter_tag');
    75169                let bwbh_portfolio_container_height = bwbh_portfolio_container.clientHeight;
    76                 let bwbh_portfolio_content = bwbh_portfolio_container.querySelector('.bw_portfolio_content');
     170                let bwbh_portfolio_content_area = bwbh_portfolio_container.querySelector('.bw_portfolio_content_area');
    77171                let bwbh_term = bwbh_clicked_tag.dataset.term;
    78                 let bwbh_num_of_words = bwbh_portfolio_content.dataset.num_of_words;
     172                let bwbh_num_of_words = bwbh_portfolio_content_area.dataset.num_of_words;
    79173           
    80174                // remove .bwbh_active_tag class from any active filter tags
     
    91185           
    92186                // create loader gif element
    93                 let bwbh_loader_gif = document.createElement('img'); // = "<img class='loader' src='" + bw_portfolio_js_vars.loader_gif_url + "'>";
     187                let bwbh_loader_gif = document.createElement('img');
    94188                bwbh_loader_gif.classList.add('loader');
    95                 bwbh_loader_gif.setAttribute('src', bw_portfolio_js_vars.loader_gif_url);
     189                bwbh_loader_gif.setAttribute('src', bwbh_portfolio_js_vars.loader_gif_url);
    96190           
    97191                if (bwbh_portfolio_container !== null && bwbh_portfolio_container_height !== null) {
     
    101195                    bwbh_portfolio_container.style.height = bwbh_portfolio_container_height + "px";
    102196                }
    103            
     197               
    104198                // clear the old set of portfolio items
    105                 if (bwbh_portfolio_content !== null) {
    106                     bwbh_portfolio_content.innerHTML = "";
     199                if (bwbh_portfolio_content_area !== null) {
     200                    bwbh_portfolio_content_area.innerHTML = "";
    107201                }
    108202           
    109203                // boolean used to handle showing tags on cards of filtered portfolio items
    110                 let bwbh_show_tags = bwbh_portfolio_content.classList.contains('show_tags');
     204                let bwbh_show_tags = bwbh_portfolio_content_area.classList.contains('show_tags');
    111205                // boolean used to handle whether modal is on or off for filtered portfolio items
    112                 let bwbh_modal_off = bwbh_portfolio_content.classList.contains('modal_off');
    113            
     206                let bwbh_modal_off = bwbh_portfolio_content_area.classList.contains('modal_off');
     207               
     208                // setup form data to send with feth request
    114209                const bwbh_data1 = new FormData();
    115                 bwbh_data1.append( 'action', 'bwbh_filter_portfolio_items' );
    116                 bwbh_data1.append( 'no_nonce_sense', bw_portfolio_js_vars.nonce );
    117210                bwbh_data1.append( 'bwbh_term', bwbh_term);
    118211                bwbh_data1.append( 'bwbh_show_tags', bwbh_show_tags);
    119212                bwbh_data1.append( 'bwbh_modal_off', bwbh_modal_off);
    120213                bwbh_data1.append( 'bwbh_num_of_words', bwbh_num_of_words);
    121            
    122                 fetch(bw_portfolio_js_vars.ajax_url, {
    123                     method: "POST",
     214
     215                // format form data as plain, then into JSON so fetch can send data as JSON
     216                const form1_data_json = JSON.stringify( Object.fromEntries( bwbh_data1.entries() ) );
     217           
     218                /**
     219                 * Make fetch request
     220                 */
     221                fetch( wpApiSettings.root + 'bw-portfolio/v1/filter-portfolio-items', {
     222                    method: 'POST',
    124223                    credentials: 'same-origin',
    125                     body: bwbh_data1
     224                    headers: {
     225                        "Content-Type": "application/json",
     226                        "Accept": "application/json",
     227                        "X-WP-Nonce": bwbh_portfolio_js_vars.nonce,
     228                    },
     229                    body: form1_data_json
    126230                })
    127                 .then((response) => response.text())
    128                 .then((data) => {                       
     231                .then( response => response.json() )
     232                .then( data => {
     233                   
     234                    console.log(data);
     235
    129236                    bwbh_loader_gif.remove();
    130                     bwbh_portfolio_content.innerHTML = data;
    131                     bwbh_portfolio_container.style.height = "auto";
    132                     clicked = false;
     237                                       
     238                    for ( let bwbh_portfolio_item_id in data.portfolio_items ) {
     239                        /**
     240                         * dynamically create card view of each portfolio item and then insert into portfolio container content area .bw_portfolio_content_area
     241                         */
     242                        const bwbh_portfolio_item_card = document.createElement("article");
     243                        bwbh_portfolio_item_card.setAttribute('class', 'bw_portfolio_item');
     244                        bwbh_portfolio_item_card.setAttribute('data-post_id', bwbh_portfolio_item_id);
     245                        bwbh_portfolio_item_card.setAttribute('data-permalink', data.portfolio_items[bwbh_portfolio_item_id].permalink);
     246
     247                        // portfolio item featured image
     248                        const bwbh_feat_image = document.createElement('img');
     249                        bwbh_feat_image.classList.add('bw_portfolio_item_image');
     250                        bwbh_feat_image.setAttribute('src', data.portfolio_items[bwbh_portfolio_item_id].feat_image_url);                       
     251                       
     252                        // add portfolio item text div for holding title, content, and tags
     253                        const bwbh_portfolio_text = document.createElement('div');
     254                        bwbh_portfolio_text.setAttribute('class', 'bw_portfolio_item_text');
     255                       
     256                        // add heading for title
     257                        const bwbh_portfolio_heading = document.createElement('h3');
     258                        bwbh_portfolio_heading.setAttribute('class', 'bw_portfolio_item_title');
     259                        bwbh_portfolio_heading.innerHTML = data.portfolio_items[bwbh_portfolio_item_id].title;
     260                        bwbh_portfolio_text.appendChild(bwbh_portfolio_heading);
     261                       
     262                        // add content div
     263                        const bwbh_portfolio_content = document.createElement('div');
     264                        bwbh_portfolio_content.setAttribute('class', 'bw_portfolio_item_content');
     265                        bwbh_portfolio_content.innerHTML = data.portfolio_items[bwbh_portfolio_item_id].content;
     266                        bwbh_portfolio_text.appendChild(bwbh_portfolio_content);
     267
     268                       
     269                        // add portfolio item tags if shortcode attribute show_tags is present or true, and a tag(s) have been sent in json response
     270                        if( data.show_tags === true && Object.keys(data.portfolio_items[bwbh_portfolio_item_id].bw_portfolio_tags).length > 0 ) {
     271
     272                            const bwbh_portfolio_tags = document.createElement('section');
     273                            bwbh_portfolio_tags.setAttribute('class', 'bw_portfolio_tags');
     274                           
     275                            const bwbh_default_span = document.createElement('span');
     276                            bwbh_default_span.innerHTML = "Tags:";
     277                            bwbh_portfolio_tags.appendChild(bwbh_default_span);
     278
     279                            for (let tag in data.portfolio_items[bwbh_portfolio_item_id].bw_portfolio_tags) {
     280                                const bwbh_portfolio_tag = document.createElement('span');
     281                                bwbh_portfolio_tag.innerHTML = data.portfolio_items[bwbh_portfolio_item_id].bw_portfolio_tags[tag];
     282                                bwbh_portfolio_tags.append( bwbh_portfolio_tag );
     283                            }
     284                            bwbh_portfolio_text.appendChild(bwbh_portfolio_tags);
     285                        }
     286
     287                        // add text div with title, content, and portfolio feaured image to the portfolio item link, or to the portfolio item
     288                        if( data.modal_off === true ) {
     289                            const bwbh_regular_link = document.createElement('a');
     290                            bwbh_regular_link.setAttribute('href', data.portfolio_items[bwbh_portfolio_item_id].permalink);
     291                            bwbh_portfolio_item_card.appendChild(bwbh_regular_link);
     292                           
     293                            bwbh_regular_link.appendChild(bwbh_feat_image);
     294                            bwbh_regular_link.appendChild(bwbh_portfolio_text);
     295                        }
     296                        else {
     297                            bwbh_portfolio_item_card.appendChild(bwbh_feat_image);
     298                            bwbh_portfolio_item_card.appendChild(bwbh_portfolio_text);
     299                        }
     300                       
     301                       
     302                        // add portfolio item card to portfolio content area
     303                        bwbh_portfolio_content_area.appendChild(bwbh_portfolio_item_card);
     304                    }
     305
     306                     // reset clicked flag variable
     307                     clicked = false;
     308 
    133309                })
    134310                .catch((error) => {
     
    154330            }
    155331        });
    156    
    157         // create modal and append to body
    158         const bw_modal = document.createElement("div");
    159         bw_modal.classList.add("bw_portfolio_modal");
    160         bw_modal.innerHTML = "<img class='loader' src='" + bw_portfolio_js_vars.loader_gif_url + "'>";
    161         document.body.appendChild(bw_modal);
    162         // select and store modal for later use
    163         bw_modal_el = document.body.querySelector('.bw_portfolio_modal');
    164332       
    165333    }
  • bw-portfolio/trunk/bw-portfolio.php

    r2668996 r2714132  
    33* Plugin Name: BW Portfolio
    44* Description: The BW Portfolio plugin is powerful yet lightweight and fast. It allows you to easily add portfolio items in your WordPress Dashboard, and organize them with portfolio tags as well. Then by using a handy shortcode you can display your portfolio items just about anywhere in a nice, responsive css grid that is compatible on many different devices. Has tag filtering of portfolio items built in.
    5 * Version: 1.1.7
     5* Version: 1.2.0
    66* Requires at least: 5.2
    77* Requires PHP: 7.0
     
    3131 * Enqueue Scripts and Stylesheets
    3232 */
    33 add_action( 'wp_enqueue_scripts', 'bwbh_portfolio_scripts_styles' );
    34 function bwbh_portfolio_scripts_styles() {
    35     wp_enqueue_style( 'bw-portfolio-style', plugins_url('/assets/css/bw-portfolio-style.css', __FILE__), [], filemtime( plugin_dir_path(__FILE__).'assets/css/bw-portfolio-style.css' ), 'screen' );
     33add_action( 'wp_enqueue_scripts', function() {
    3634   
    37     wp_enqueue_script( 'bw-portfolio-script', plugins_url('/assets/js/bw-portfolio-script.js', __FILE__), array(), '1.1.0', true );
    38     wp_localize_script( 'bw-portfolio-script', 'bw_portfolio_js_vars', [
    39         'ajax_url' => admin_url( 'admin-ajax.php' ),
    40         'nonce' => wp_create_nonce( 'bw_portfolio_secret_nonce_sauce' ),
    41         'loader_gif_url' => esc_url( plugins_url( 'bw-portfolio/assets/images/loading.gif', dirname(__FILE__) ) ),
    42     ]);
    43 }
    44 
    45 
    46 /**
    47  * handle fetch requests to load portfolio items content in modal
    48  */
    49 add_action('wp_ajax_bwbh_fetch_portfolio_item', 'bwbh_fetch_portfolio_item');
    50 add_action('wp_ajax_nopriv_bwbh_fetch_portfolio_item', 'bwbh_fetch_portfolio_item');
    51 function bwbh_fetch_portfolio_item() {
    52    
    53     check_ajax_referer( 'bw_portfolio_secret_nonce_sauce', 'no_nonce_sense' );
    54    
    55    
    56     if($_POST['post_id']) {
    57         $bw_post_id = (int)$_POST['post_id'];
    58         $bw_post = get_post($bw_post_id);
    59        
    60         // need to get the shortcode attribute settings that might affect full portfolio item view here
    61         $bwbh_show_tags = filter_var($_POST['bwbh_show_tags'], FILTER_VALIDATE_BOOLEAN);
    62        
    63         $bw_output = include plugin_dir_path( __FILE__ ) . 'inc/portfolio-item-full.php';
    64        
    65         echo wp_kses_post($bw_output);
     35    if( !is_admin() ) {
     36        wp_enqueue_style( 'bw-portfolio-style', plugins_url('/assets/css/bw-portfolio-style.css', __FILE__), ['dashicons'], filemtime( plugin_dir_path(__FILE__).'assets/css/bw-portfolio-style.css' ), 'screen' );
     37        wp_enqueue_script( 'bw-portfolio-script', plugins_url('/assets/js/bw-portfolio-script.js', __FILE__), ['wp-api-request'], '1.2.0', true );
     38
     39        wp_localize_script( 'bw-portfolio-script', 'bwbh_portfolio_js_vars', [
     40            'ajax_url' => admin_url( 'admin-ajax.php' ),
     41            'nonce' => wp_create_nonce( 'wp_rest' ),
     42            'loader_gif_url' => esc_url( plugins_url( 'bw-portfolio/assets/images/loading.gif', dirname(__FILE__) ) ),
     43        ]);
     44    }
     45   
     46});
     47
     48
     49
     50add_action('rest_api_init', function () {
     51
     52    // custom route to view full portfolio item
     53    register_rest_route('bw-portfolio/v1', '/view-portfolio-item', [
     54        'methods' => 'POST',
     55        'callback' => 'bw_view_portfolio_item',
     56        'permission_callback' => '__return_true',
     57    ]);
     58
     59    // custom route to filter portfolio items
     60    register_rest_route('bw-portfolio/v1', '/filter-portfolio-items', [
     61        'methods' => 'POST',
     62        'callback' => 'bw_filter_portfolio_items',
     63        'permission_callback' => '__return_true',
     64    ]);
     65});
     66
     67/**
     68 * handle fetch requests to load full view of portfolio item in modal via rest api
     69 */
     70function bw_view_portfolio_item( $request ) {   
     71    // if $request includes the portfolio item id...
     72    if( $request['bw_portfolio_item_id'] ) {
     73
     74        $bw_portfolio_item_id = (int)$request['bw_portfolio_item_id'];
     75        $bw_post = get_post($bw_portfolio_item_id, OBJECT, 'display');
     76       
     77        // if portfolio item was found...
     78        if($bw_post !== null) {
     79
     80            // store the needed post data to send back in rest response
     81            $bw_response_data = [
     82                'title' => esc_html__($bw_post->post_title),
     83                'content' => apply_filters('the_content', $bw_post->post_content ),
     84            ];
     85           
     86            // get and add tags to $bw_post_data
     87            if( $bw_show_tags !== false ) {
     88                $bw_portfolio_tags = get_the_terms( $bw_post->ID, 'portfolio_tag' );
     89                if( !is_wp_error($bw_portfolio_tags) && $bw_portfolio_tags !== false ) {
     90                    foreach( $bw_portfolio_tags as $bw_tag ) {
     91                        $bw_response_data['bw_portfolio_tags'][$bw_tag->term_id] = $bw_tag->name;
     92                    }
     93                }
     94            }
     95
     96            // add featured image url to array
     97            $post_thumbnail_id = get_post_thumbnail_id( $bw_post->ID );
     98            if(!empty($post_thumbnail_id)) {
     99                $feat_image_urls = wp_get_attachment_image_src( $post_thumbnail_id, 'full' );
     100                $bw_response_data['feat_image_url'] = $feat_image_urls[0];
     101            }
     102
     103            // store the shortcode attribute settings that affect full portfolio item view here
     104            $bw_show_tags = filter_var($request['bw_show_tags'], FILTER_VALIDATE_BOOLEAN);
     105            $bw_response_data['bw_show_tags'] = $bw_show_tags;
     106
     107            return new WP_REST_Response( $bw_response_data, 200 );
     108        }
     109        else {
     110            // Return a WP_Error because the request product was not found. In this case we return a 404 because the main resource was not found.
     111            return new WP_Error( 'rest_portfolio_item_invalid', esc_html__( 'The portfolio item does not exist.', 'bw_portfolio' ), array( 'status' => 404 ) );
     112        }
     113    }
     114}
     115
     116
     117/**
     118 * handle fetch requests to filter portfolio items by portfolio tag
     119 */
     120function bw_filter_portfolio_items( $request ) {
     121   
     122    if( $request['bwbh_term'] ) {
     123       
     124        $bwbh_term = $request['bwbh_term'];
     125       
     126        // need to get the shortcode attribute settings that might affect filtered results here
     127        $bwbh_show_tags = filter_var($request['bwbh_show_tags'], FILTER_VALIDATE_BOOLEAN);
     128        $bwbh_modal_off = filter_var($request['bwbh_modal_off'], FILTER_VALIDATE_BOOLEAN);
     129        $bwbh_num_of_words = (int)$request['bwbh_num_of_words'];
     130
     131        $bw_response_data = [
     132            'term' => $bwbh_term,
     133            'show_tags' => $bwbh_show_tags,
     134            'modal_off' => $bwbh_modal_off,
     135            'num_of_words' => $bwbh_num_of_words,
     136        ];
     137
     138        // include file that builds custom WP_Query for filtered portfolio items
     139        include plugin_dir_path( __FILE__ ) . 'inc/portfolio-query.php';
     140
     141        // loop through query results, and only add relevant pieces of portfolio item data to the $bw_response_data array
     142        if ( $bw_portfolio_query->have_posts() ) :
     143            while ( $bw_portfolio_query->have_posts() ) : $bw_portfolio_query->the_post();
     144                $bw_response_data['portfolio_items'][get_the_ID()] = [
     145                    'title' => get_the_title(),
     146                    'content' => wp_kses_post( bwbh_limit_words( strip_tags( get_the_content() ), $bwbh_num_of_words ) ) . '...',
     147                ];
     148               
     149                // if modal_off is true, then send permalink for opening portfolio item manually
     150                if( $bwbh_modal_off ) {
     151                    $bw_response_data['portfolio_items'][get_the_ID()]['permalink'] = get_permalink( get_the_ID() );
     152                }
     153
     154                // get and add portfolio item tags to $bw_response_data if show_tags shortcode attribute was true
     155                if( $bwbh_show_tags !== false ) {
     156                    $bw_portfolio_tags = get_the_terms( get_the_ID(), 'portfolio_tag' );
     157                    if( !is_wp_error($bw_portfolio_tags) && $bw_portfolio_tags !== false ) {
     158                        foreach($bw_portfolio_tags as $bw_tag) {
     159                            $bw_response_data['portfolio_items'][get_the_ID()]['bw_portfolio_tags'][$bw_tag->term_id] = $bw_tag->name;
     160                        }
     161                    }
     162                }
     163
     164                // add featured image url to array if it exists
     165                $post_thumbnail_id = get_post_thumbnail_id( $bw_post->ID );
     166                if(!empty($post_thumbnail_id)) {
     167                    $feat_image_urls = wp_get_attachment_image_src( $post_thumbnail_id, 'full' );
     168                    $bw_response_data['portfolio_items'][get_the_ID()]['feat_image_url'] = $feat_image_urls[0];
     169                }
     170               
     171
     172            endwhile;
     173           
     174            wp_reset_postdata();
     175
     176        endif; // else if no portfolio items in tag are found then return error?(don't think this can happen though?)
     177
     178        return new WP_REST_Response( $bw_response_data, 200 );
     179       
    66180    }
    67181       
     
    69183}
    70184
    71 
    72 /**
    73  * handle fetch requests to filter portfolio items by portfolio tag
    74  */
    75 add_action('wp_ajax_bwbh_filter_portfolio_items', 'bwbh_filter_portfolio_items');
    76 add_action('wp_ajax_nopriv_bwbh_filter_portfolio_items', 'bwbh_filter_portfolio_items');
    77 function bwbh_filter_portfolio_items() {
    78    
    79     check_ajax_referer( 'bw_portfolio_secret_nonce_sauce', 'no_nonce_sense' );
    80    
    81     if($_POST['bwbh_term']) {
    82        
    83         $bwbh_term = $_POST['bwbh_term'];
    84        
    85         // need to get the shortcode attribute settings that might affect filtered results here
    86         $bwbh_show_tags = filter_var($_POST['bwbh_show_tags'], FILTER_VALIDATE_BOOLEAN);
    87         $bwbh_modal_off = filter_var($_POST['bwbh_modal_off'], FILTER_VALIDATE_BOOLEAN);
    88         $bwbh_num_of_words = (int)$_POST['bwbh_num_of_words'];
    89        
    90         // include file that builds custom WP_Query for filtered portfolio items
    91         include plugin_dir_path( __FILE__ ) . 'inc/portfolio-query.php';
    92        
    93         include plugin_dir_path( __FILE__ ) . 'inc/portfolio-filtered-loop.php';
    94        
    95     }
    96        
    97     wp_die();
    98 }
    99 
    100185/**
    101186* Register bw_portfolio post type
    102187* Register portfolio_tag taxonomy
    103188*/
    104 add_action( 'init', 'bwbh_portfolio_cpt_init' );
    105 function bwbh_portfolio_cpt_init() {
     189add_action( 'init', function() {
    106190    //
    107191    register_post_type('bw_portfolio',
     
    151235    unset( $bw_tax_labels );
    152236    unset( $bw_tax_args );
    153 }
    154 
    155 /**
    156 * Helper functions for the [bw_portfolio] Shortcode
    157 */
     237});
     238
     239/**
     240* Helper functions for the [bw_portfolio] Shortcode attributes
     241*/
     242
     243// num_of_words_on_cards shortcode attribute
    158244if (!function_exists('bwbh_limit_words')) {
    159245    function bwbh_limit_words($string, $word_limit) {
    160246        $words = explode(" ", trim($string));
    161         return implode(" ",array_splice($words, 0, $word_limit));
     247        return implode(" ", array_splice($words, 0, $word_limit));
    162248    }
    163249}
    164250
     251// shortcode attributes with no value set are normally sent through as "indexed key => attribute name" pairs, where normal shortcode attributes with a value set are sent as "attribute name => att value"
     252// a number of the shortcode attributes I have added are simply flag variables, so to make it easier for users they can simply enter the attribute name, instead of attribute name=true
    165253if (!function_exists('bwbh_normalize_empty_atts')) {
    166254    function bwbh_normalize_empty_atts($atts) {
    167255        foreach ($atts as $attribute => $value) {
    168             // empty value atts are sent through as "indexed key => attribute name" pairs, where normal atts with value set are sent as "attribute name => att value"
    169             // so, if key is an integer then that was an attribute set with no value, and we want to unset that, and add attr name into the atts array with value true
     256            // if $attribute/key is an integer then that was an attribute set with no value, and we want to unset that, and re-add the attr name into the $atts array with boolean value true
    170257            if (is_int($attribute)) {
    171258                $atts[strtolower($value)] = true;
  • bw-portfolio/trunk/inc/portfolio-loop.php

    r2668996 r2714132  
    66if ( $bw_portfolio_query->have_posts() ) :
    77
    8     $portfolio_output .= "<section class='bw_portfolio_container'>\n";
     8    $portfolio_output .= "<section class='bw_portfolio_container alignwide'>\n";
    99
    1010    $portfolio_output .= "<header class='bw_portfolio_header'>\n";
     
    1313        $portfolio_output .= "<h2 class='bw_portfolio_heading'>" .  esc_html__( $bw_atts['portfolio_title'] ) . "</h2>\n";
    1414    }
    15     // portfolio tag filter links
     15    /**
     16     * portfolio tag filter links
     17     */
    1618    $bw_portfolio_tags = get_terms([
    1719        'taxonomy' => 'portfolio_tag',
    18         //'hide_empty' => true, // default is already true
     20        'orderby' => 'id'
    1921    ]);
    2022    if( !is_wp_error($bw_portfolio_tags) && count($bw_portfolio_tags) > 0 ) {
    2123       
    2224        $portfolio_output .= "<section class='portfolio_filter_tags'>\n";
    23        
    24         $portfolio_output .= "<a href='#' class='portfolio_filter_tag' data-term='show_all'>Show All</a>\n";
    25        
     25               
     26        $portfolio_output .= "<a class='portfolio_filter_tag' data-term='show_all' title='Show all portfolio items'>Show All</a>\n";
     27
    2628        foreach($bw_portfolio_tags as $bw_tag) {
    27             $portfolio_output .= "<a href='#' class='portfolio_filter_tag' data-term='" . $bw_tag->slug . "'>" . $bw_tag->name . "</a>\n";
     29            if( isset($bw_tags_array) ) {
     30                if( in_array($bw_tag->slug, $bw_tags_array) ) {
     31                    $portfolio_output .= "<a class='portfolio_filter_tag' data-term='" . $bw_tag->slug . "' title='Show portfolio items tagged with " . $bw_tag->name . "'>" . $bw_tag->name . "</a>\n";
     32                }
     33            }
     34            else {
     35                $portfolio_output .= "<a class='portfolio_filter_tag' data-term='" . $bw_tag->slug . "' title='Show portfolio items tagged with " . $bw_tag->name . "'>" . $bw_tag->name . "</a>\n";
     36            }
     37           
    2838        }
    2939       
     
    7686    $data_num_of_words = "data-num_of_words='" . $num_of_words . "'";
    7787   
    78     $portfolio_output .= "<div class='bw_portfolio_content" . $bw_content_classes . "' " . $data_num_of_words . ">\n";
     88    $portfolio_output .= "<div class='bw_portfolio_content_area" . $bw_content_classes . "' " . $data_num_of_words . ">\n";
    7989
    8090    while ( $bw_portfolio_query->have_posts() ) : $bw_portfolio_query->the_post();
    8191        // add portfolio item html to output variable
    82         $portfolio_output .= "<article class='bw_portfolio_item' data-post_id='" . get_the_ID() . "'>\n";
     92        $portfolio_output .= "<article class='bw_portfolio_item' data-post_id='" . get_the_ID() . "' data-permalink='" . get_the_permalink() . "' >\n";
    8393       
    8494        // if user adds modal_off attribute, then add html anchor link to handle normal anchor link click to the full portfolio item being displayed by theme template
     
    8797        }
    8898       
    89         $portfolio_output .= "<img src='" . esc_url( get_the_post_thumbnail_url(get_the_ID(), 'post-thumbnail') ) . "' class='bw_portfolio_item_image'>";
     99        $portfolio_output .= "<img src='" . esc_url( get_the_post_thumbnail_url(get_the_ID(), 'large') ) . "' class='bw_portfolio_item_image'>";
    90100       
    91101        $portfolio_output .= "<div class='bw_portfolio_item_text'>\n";
  • bw-portfolio/trunk/inc/portfolio-query.php

    r2668990 r2714132  
    1616    $bw_tags_array = explode(',', $bw_tags_str);
    1717    // for each array value make spaces between words into dashes, and make words lowercase
    18     array_map('sanitize_title_with_dashes', $bw_tags_array);
     18    $bw_tags_array = array_map('sanitize_title_with_dashes', $bw_tags_array);
    1919    // trim whitespace off ends of tags
    20     array_map('trim', $bw_tags_array);
     20    $bw_tags_array = array_map('trim', $bw_tags_array);
    2121   
    2222    // if user clicked on portfolio tag filter link, empty array of filter terms from shortcode, and insert only the term user clicked on
    2323    if(!empty($bwbh_term)) {
    2424        $bw_tags_array = [];
    25         $bw_tags_array[] = strip_tags($bwbh_term);
     25        $bw_tags_array[] = strip_tags(trim($bwbh_term));
    2626    }
    2727   
  • bw-portfolio/trunk/readme.txt

    r2668996 r2714132  
    66Tested up to: 5.9
    77Requires PHP: 7.0
    8 Stable tag: 1.1.7
     8Stable tag: 1.2.0
    99License: GPLv3
    1010License URI: https://www.gnu.org/licenses/gpl-3.0.html
     
    3333
    3434== Changelog ==
     35
     36= 1.2.0 =
     37* Major update where I stopped using admin-ajax and switched over to using the WordPress REST API for viewing a portfolio item, and filtering portfolio items by portfolio tag
     38* added .alignwide class to portfolio container so wordpress themes(like twentytwentone, etc) default styling allows it to be full width of its parent
     39* fixed some minor display bugs
    3540
    3641= 1.1.7 =
Note: See TracChangeset for help on using the changeset viewer.