Magento 2.0 modul fejlesztés lépésről lépésre – 4. rész (Knockout JS)

Tartalom:

  • Mi az a Knockout.js?
  • Miért kell / érdemes használni és mire jó?
  • Alap példa modul felépítése
  • Block-ok és layout létrehozása
  • Knockout JS használata PHTML template fájlban
  • Saját CustomerData osztály létrehozása és implementálása
  • Knockout JS használata HTML template fájlként

Mi az a Knockout.js?

A Knockout.js egy nyílt forráskódú egyszerűsített dinamikus javascript, mely egy Model-View-View Model (MVVM) rendszer/framework.

 

Miért kell / érdemes használni és mire jó?

Természetesen nem kell használni, viszont mivel a Magento 2.0 szerves részét képezi a full page cache használata miatt is, így a saját modulunkban megvalósítandó üzleti logika alapján célszerű elgondolkozni a használatán a fejlesztés megkezdése előtt. Anélkül, hogy különálló bonyolult javascript-ek készítenénk saját modulunkban, egy könnyű megoldással férhetünk hozzá customer, product, order stb. adatokhoz a frontenden és kezelhetjük azokat.

Alap példa modul felépítése

 

A jelenlegi cikkben is a már korábban használt alap példa modult fogjuk használni. A Magento 2.0-ban a Knockout JS jelentős szerepet kap, így érdemes vele megismerkedni.

 

Mivel korábbi cikkünk az alap modul felépítésére már kitért, most csak a modul felépítése kerül bemutatásra.

 

magento 2 modul szerkezet

 

Block-ok és layout létrehozása

 

A példához egy block-ot hozunk létre és két külön template fájlt, amiben két különböző módszerrel használjuk a Knockout JS-t. Az első példában magában a template fájlban használjuk a Knockout JS-t, míg a másodikban ennek segítségével egy másik HTML template fájlt implementálunk vagy úgy is fogalmazhatnánk, hogy töltünk be. A két külön példához első lépésben egy layout fájlt hozunk létre, és aszerint, hogy melyik block-ot használjuk, most az egyszerűség kedvéért csak kikommentezzük a megfelelőt.

Ehhez szükségünk lesz egy default.xml fájlra, ami az alábbi helyen található a példa modulunkban: app/code/Aion/Sample/view/frontend/layout/default.xml. A fájl tartalma:

 

<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
     <body>
         <referenceBlock name="sidebar.additional">
             <!-- First sample -->
             <block class="Aion\Sample\Block\Sample" name="aion.sample.knockout.sidebar" template="Aion_Sample::sidebar.phtml" after="wishlist_sidebar"/>
             <!-- Second Sample -->
             <!--<block class="Aion\Sample\Block\Sample" name="aion.sample.knockout.sidebar.second" template="Aion_Sample::second-sidebar.phtml" after="wishlist_sidebar"/>-->
         </referenceBlock>
     </body>
 </page>

 

A layout fájlban a példa kedvéért a saját block-unk és a hozzá tartozó template fájl az oldalsáv (sidebar) alatti részen fog megjelenni (<referenceBlock name=”sidebar.additional”>).

 

A következő lépésben a block osztály kerül létrehozásra, ami a fenti layout-ban is látható. Az alábbi helyen található a példa modulunkban: app/code/Aion/Sample/Block/Sample.php. A fájl tartalma:

 

<?php
 namespace Aion\Sample\Block;
 use Magento\Framework\View\Element\Template;
 use Aion\Sample\Helper\Data as DataHelper;
 
 class Sample extends Template
 {
     /**
      * @var DataHelper
      */
     protected $_helper;
 
     /**
      * @param Template\Context $context
      * @param DataHelper $dataHelper
      * @param array $data
      */
     public function __construct(
         Template\Context $context,
         DataHelper $dataHelper,
         array $data = []
     ) {
         $this->_helper = $dataHelper;
         parent::__construct($context, $data);
         $this->_isScopePrivate = true;
     }
 
     /**
      * Get extension helper
      *
      * @return DataHelper
      */
     public function getExtensionHelper()
     {
         return $this->_helper;
     }
 
     /**
      * Sample items for example
      *
      * @return array
      */
     public function getInitSecondItems()
     {
         $sampleData = $this->_helper->getSampleProductNames();
 
         return $sampleData;
     }
 }

 

A block-ban látható getInitSecondItems() függvény a második példában fog szerepet kapni, így erre később térünk ki.

 

Knockout JS használata PHTML template fájlban

 

A Knockout JS bemutatásához szükséges még a megfelelő javascript fájlok elkészítése is. Első lépésben definiáljuk a saját modulunkhoz tartozó javascript fájlt, amit a requirejs-config.js-ban teszünk meg. Ez az alábbi helyen található a példa modulunkban: app/code/Aion/Sample/frontend/requirejs-config.js. A fájl tartalma:

var config = {
     map: {
         '*': {
             sample: 'Aion_Sample/js/view/sample-sidebar'
         }
     }
 };

 

Itt adjunk meg, hogy a modulunkban hol található a saját javascript fájlunk.

 

Természetesen szükség van a sample-sidebar.js fájlra is. Ez az alábbi helyen található a példa modulunkban: app/code/Aion/Sample/frontend/web/js/view/sample-sidebar.js. A fájl tartalma:

 

define([
     'ko',
     'uiComponent',
     'Magento_Customer/js/customer-data',
     'mage/translate'
 ], function (ko, Component, customerData, $t) {
     'use strict';
     return Component.extend({
         // Second example
         defaults: {
             template: 'Aion_Sample/second-sidebar'
         },
         displayContent: ko.observable(true),
         initialize: function () {
             this._super();
             this.sample = customerData.get('sample');
             // Second example
             this.someText = $t('Sample content with template.');
             // Second example. foreach examples
             this._showItems();
             // Second example, foreach example
             this._showMonths();
             // Second example, other foreach example
             this._showCategories();
             // Second example and another foreach example
             this._showMyItems();
         },
         getInfo: function() {
             return this.sample().info || this.initFullname || customerData.get('customer')().fullname;
         },
         getCartItemsCountText: function () {
             return this.sample().cart_items_text;
         },
         getCartItemsCount: function () {
             return this.sample().cart_count;
         },
         getHint: function() {
             return this.sample().hint || this.initHint;
         },
         _showItems: function() {
             var self = this;
             if (typeof this.initSampleData !== "undefined") {
                 self.sampleItems = JSON.parse(this.initSampleData);
             }
         },
         _showMonths: function() {
             var self = this;
             self.months = [ 'Jan', 'Feb', 'Mar', 'etc' ];
         },
         _showCategories: function() {
             var self = this;
             self.categories = [
                 { name: 'Fruit', items: [ 'Apple', 'Orange', 'Banana' ] },
                 { name: 'Vegetables', items: [ 'Celery', 'Corn', 'Spinach' ] }
             ];
         },
         _showMyItems: function() {
             var self = this;
             self.myItems = [ 'First', 'Second', 'Third' ]
         }
     });
 });

 

A példában szereplő függvények előtt jelzésre került, hogy melyek tartoznak a később bemutatandó második példához. Ezen objektumok és függvények előtt a //Second example látható.

Nézzük meg egy kicsit a működést!

A javascript a Magento 2.0-ban lévő uiComponent objektumot terjeszti ki, majd definiálásra kerül a saját sample objektum is, mely a CustomerData része lesz (this.sample = customerData.get (‘sample’);). Még négy függvény kerül a példában definiálásra, melyeket a template fájl fogunk használni.

 

Függvények:

  • getInfo() – a vevő (customer) nevének megjelenítésért felelős.
  • getCartItemsCountText() – string példa szöveggel, melynek része a kosárban lévő termékek száma.
  • getCartItemsCount() – integer, ami a kosárban lévő termékek számát tartalmazza (nem az összmennyiséget, sum qty)
  • getHint() – string, csak információt tartalmaz

 

Az említett függvények által visszaadott adatokat a 4. pontban leírt Sample osztály fogja kezelni.

 

Nézzük a block-hoz tartozó template fájl tartalmát, ami az alábbi helyen található a példa modulunkban: app/code/Aion/Sample/frontend/templates/sidebar.phtml. A fájl tartalma:

 

<?php
 $sampleHelper = $block->getExtensionHelper();
 $initFullName = $sampleHelper->getIsLoggedIn() ? $sampleHelper->getCustomerFullName() : __('You are logged out.');
 ?>
 <?php if ($sampleHelper->isEnabled()) : ?>
     <div class="block block-compare block-aion-sample" data-bind="scope: 'sample'">
         <div class="block-title">
             <strong><?php /* @escapeNotVerified */ echo __('Aion Sample Block'); ?></strong>
         </div>
         <div class="block-content">
             <strong class="subtitle" style="display: inline-block">
                 <?php /* @escapeNotVerified */ echo __('Customer Info:') ?>
             </strong>
             <p class="description">
                 <span data-bind="text: getInfo()"></span><br />
             </p>
             <p class="description">
                 <!-- ko if: getCartItemsCount() -->
                 <strong class="subtitle" style="display: inline-block">
                     <?php /* @escapeNotVerified */ echo __('Cart Info:') ?>
                 </strong>
                 <br />
                 <span data-bind="text: getCartItemsCountText()"></span>
                 <!-- /ko -->
             </p>
             <p class="hint"><small data-bind="text: getHint()"></small></p>
         </div>
     </div>
     <script type="text/x-magento-init">
     { "*": {
             "Magento_Ui/js/core/app": {
                 "components": {
                     "sample": {
                         "component": "Aion_Sample/js/view/sample-sidebar",
                         "initHint": "<?php echo __('(Refresh automatically after cart modification)') ?>",
                         "initFullname": "<?php echo $initFullName ?>"
                     }
                 }
             }
         }
     }
     </script>
 <?php endif; ?>

 

Lényegében itt látható a Knockout JS használata. Nézzük, részletesen hol használjuk, és hogyan működik.

 

<span data-bind=”text: getInfo()”></span>

A javascript fájlban definiált getInfo() függvény által visszaadott string fog megjelenni a <span> tag-ben.

 

Knockout JS:

 

<!– ko if: getCartItemsCount() –>

<strong class=”subtitle” style=”display: inline-block”>

<?php /* @escapeNotVerified */ echo __(‘Cart Info:’) ?>

</strong>

<br />

<span data-bind=”text: getCartItemsCountText()”></span>

<!– /ko –>

 

Amennyiben a javascript fájlban definiált getCartItemsCount() függvény értéke nem 0, akkor megjelenítésre kerül az if szekcióban lévő rész. A getCartItemsCountText() függvény pedig egy string-gel tölti ki a <span> tag-et.

 

<p class=”hint”><small data-bind=”text: getHint()”></small></p>

A getHint() függvény egy string-gel tölti ki a <small> tag-et.

 

Nézzük meg, hogy ezek után hogyan jelenik meg az elkészült block a Magento 2.0 sidebar részében.

 

magento 2 modul fejlesztés sidebar

 

Hogyan módosíthatjuk ezeket az adatokat különböző felhasználói interakciókra? Erre kapunk választ a következő pontban.

 

Saját CustomerData osztály létrehozása és implementálása

 

Ahhoz, hogy a fenti block-unk és annak tartalma interaktívan módosuljon, szükségünk van egy saját CustomerData osztályra, mely a javascript-ben definiált sample osztályt szolgálja ki adatokkal.

Az osztály az alábbi helyen található a példa modulunkban: app/code/Aion/Sample/CustomerData/Sample.php. A fájl tartalma:

 

<?php
 namespace Aion\Sample\CustomerData;
 use Magento\Customer\CustomerData\SectionSourceInterface;
 use Aion\Sample\Helper\Data as DataHelper;
 /**
  * Sample section
  */
 class Sample implements SectionSourceInterface
 {
     /**
      * @var DataHelper
      *
     protected $_helper;
 
     /**
      * @param DataHelper $dataHelper
      */
     public function __construct(
         DataHelper $dataHelper
     ) {
         $this->_helper = $dataHelper;
     }
 
     /**
      * {@inheritdoc}
      */
     public function getSectionData()
     {
         $sampleData = $this->_getSampleData();

         return $sampleData;
     }
 
     /**
      * First sample data example
      *
      * @return array
      */
     protected function _getSampleData()
     {
         $sampleData = [
             'info' => __('You are logged out.')
         ];
         $isLoggedIn = $this->_helper->getIsLoggedIn();
         if ($isLoggedIn) {
             $sampleData = [
                 'info' => __('You are logged in as: %1', $this->_helper->getCustomerFullName())
             ];
         }
 
         $cartItemsCount = $this->_helper->getCartItemCount();
         $sampleData = array_merge(
             $sampleData,
             [
                 'cart_items_text' => __('You have %1 item(s) in your cart', $cartItemsCount),
                 'cart_count' => (int)$cartItemsCount,
                 'hint' => __('(Refresh automatically after cart modification)')
             ]
         );
 
         return $sampleData;
     }
 }

 

Az osztályban a getSectionData() függvény szolgálja ki adatokkal a javascript-ben definiált sample osztályt. Itt fontos megjegyezni, hogy a két osztályt célszerű azonos néven elnevezni a PHP és Javascript kódban.

A _getSampleData egy tömbbel tér vissza, melynek kulcsai megegyeznek a javascript kódban hasznát értékekkel. Itt implementálhatjuk a szükséges üzleti logikát. A példa kedvéért itt csak a customer teljes nevének átadása és kosár elemek számolása történik meg.

Szükséges még definiálni a CustomerData osztályt is, hogy a Magento 2.0 rendszer „tudjon” róla. Ezt a di.xml fájlban kell megadnunk, ami az alábbi helyen található a példa modulunkban: app/code/Aion/Sample/etc/frontend/di.xml. A fájl tartalma:

 

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
     <type name="Magento\Customer\CustomerData\SectionPoolInterface">
         <arguments>
             <argument name="sectionSourceMap" xsi:type="array">
                 <item name="sample" xsi:type="string">Aion\Sample\CustomerData\Sample</item>
             </argument>
         </arguments>
     </type>
 </config>

 

A következő fontos lépés, hogy a Sample osztályban definiált getSectionData() függvény mikor fusson le, milyen felhasználói interakcióra. Ehhez szükséges egy xml fájlban megadni ezen controller-ek listáját.

 

Ezt a sections.xml fájlban kell definiálnunk, ami az alábbi helyen található a példa modulunkban: app/code/Aion/Sample/etc/frontend/sections.xml. A fájl tartalma:

 

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Customer:etc/sections.xsd">
     <action name="checkout/cart/add">
         <section name="sample"/>
     </action>
     <action name="checkout/cart/delete">
         <section name="sample"/>
     </action>
     <action name="checkout/cart/updatePost">
         <section name="sample"/>
     </action>
     <action name="checkout/cart/updateItemOptions">
         <section name="sample"/>
     </action>
     <action name="checkout/sidebar/removeItem">
         <section name="sample"/>
     </action>
     <action name="checkout/sidebar/updateItemQty">
         <section name="sample"/>
     </action>
 </config>

 

Az xml-ben látható, milyen controller action-öket definiáltunk. Ezek a termék kosárba helyezése, törlése, termék számának frissítése stb.

De mit is jelent pontosan ez?

Miután elkészültünk a fenti Sample osztállyal, az említett getSectionData() függvény le fog futni ezen controller action-ök futása esetén, és a frontend-en megjelenő block-unk automatikusan ajax-szal frissülni fog a getSectionData() függvény által visszaadott adatokkal. Mindez anélkül történik, hogy bármilyen ajax függvényt és kezelést definiáltunk, írtunk volna a javascript fájlunkban.

 

Bejelentkezett felhasználóval így néz ki a block-unk miután egy tetszőleges terméket a kosárba helyeztünk:

 

magento 2 modul fejlesztés kosár

 

 

Az ajax hívásban visszakapott objektum tartalma:

 

magento 2 modul fejlesztés ajax tartalom

 

 

 

Knockout JS használata HTML template file-ként

 

Az előző példában a phtml fájlban használtuk közvetlenül a Knockout JS-t. A második esetben is ezt fogjuk alkalmazni, azonban egy külön html template fájlt használunk az adatok megjelenítésére.

Ehhez első lépésben egy második phtml példa fájlt hozunk létre. A fájl a modulunkban az app/code/Aion/Sample/view/frontend/templates/second-sidebar.phtml. A fájl tartalma:

 

<?php
 $sampleHelper = $block->getExtensionHelper()
 ?>
 <?php if ($sampleHelper->isEnabled()) : ?>
     <div class="block block-compare block-aion-sample" data-bind="scope: 'sample'">
         <div class="block-title">
             <strong><?php /* @escapeNotVerified */ echo __('Aion Second Sample Block'); ?></strong>
         </div>
         <div class="block-content">
             <!-- ko template: getTemplate() -->
             <!-- /ko -->
         </div>
     </div>
     <script type="text/x-magento-init">
     {
         "*": {
             "Magento_Ui/js/core/app": {
                 "components": {
                     "sample": {
                         "component": "Aion_Sample/js/view/sample-sidebar",
                         "initSampleData": "<?php echo addslashes(json_encode($block->getInitSecondItems())) ?>"
                     }
                 }
             }
         }
     }
     </script>
 <?php endif; ?>

 

Az első példában bemutatott phtml fájlhoz képest a különbség jól látható. A tartalmi részben kerül implementálásra a html template behívása:

<!– ko template: getTemplate() –>

<!– /ko –>

 

Itt fontos visszatérni a cikk elején bemutatott sample-sidebar.js fájlra és figyelembe venni a //Second sample kód részeket, mert a második példánkat ezek kezelik. Emellett az 1. pontban részletezett default.xml fájlban a második block-ot használjuk csak:

<block class=”Aion\Sample\Block\Sample” name=”aion.sample.knockout.sidebar.second” template=”Aion_Sample::second-sidebar.phtml” after=”wishlist_sidebar”/>

Az elsőt kikommentezzük.

 

A következő lépésben létrehozzuk a HTML fájlt, ami az app/code/Aion/Sample/view/frontend/web/template/second-sidebar.html. A fájl tartalma:

 

<!-- ko if: displayContent() -->
     <p data-bind="text: someText"></p>
 
     <h4>Items Form Block Class</h4>
     <ul data-bind="foreach: sampleItems">
         <li>
             <span data-bind="text: $data"> </span>
         </li>
     </ul>
 
     <h4>Months</h4>
     <ul data-bind="foreach: months">
         <li>
             <span data-bind="text: $data"> </span>
         </li>
     </ul>
 
     <h4>Categories</h4>
     <ol data-bind="foreach: { data: categories, as: 'category' }">
         <li>
             <ul data-bind="foreach: { data: items, as: 'item' }">
                 <li>
                     <span data-bind="text: category.name"></span>:
                     <span data-bind="text: item"></span>
                 </li>
             </ul>
         </li>
     </ol>
 
     <h4>My Items</h4>
     <ul>
         <!-- ko foreach: myItems -->
         <li>Item name: <span data-bind="text: $data"></span></li>
         <!-- /ko -->
     </ul>
 
 <!-- /ko -->
 <!-- ko ifnot: displayContent() -->
     <p class="empty-text" data-bind="text: $t('Content is empty.')"></p>
 <!-- /ko -->

 

A sample-sidebar.js-t elemezve nézzük át a HTML template fájl működését. Elsőként definiálva maga a template fájl és egy true érték, mely az if feltételben szerepel. Természetesen ezt a szükséges üzleti logika szerint lehet módosítani.

 

// Second example

defaults: {

template: ‘Aion_Sample/second-sidebar’

},

displayContent: ko.observable(true),

 

A példa adatokat szolgáló függvények automatikusan lefutnak az inicializálás során:

 

// Second example

this.someText = $t(‘Sample content with template.’);

// Second example. foreach examples

this._showItems();

// Second example, foreach example

this._showMonths();

// Second example, other foreach example

this._showCategories();

// Second example and another foreach example

this._showMyItems();

 

Ezek közül ‒ a példa céljából ‒ csak egyetlen adatai érkeznek a block osztályból (app/code/Aion/Sample/Block/Sample.php). Az átadása a phtml fájlban történik:

 

“initSampleData”: “<?php echo addslashes(json_encode($block->getInitSecondItems())) ?>”

 

A getInitSecondItems() függvény a helper osztályban van megvalósítva:

 

/**
  * Get sample product names
  *
  * @return array
  */
 public function getSampleProductNames()
 {
     $sampleData = [];
     /* @var $product Product */
     $product = $this->_productFactory->create();
     /* @var $collection Collection */
     $collection = $product->getCollection();
     $collection->setVisibility($this->_catalogProductVisibility->getVisibleInCatalogIds());
     $collection->addStoreFilter()->addAttributeToSelect(
         ['name']
     );
     $collection->getSelect()->orderRand('e.entity_id');
     $collection->setPageSize(
         5
     )->setCurPage(
         1
     )->toArray(['name']);
 
     /* @var $item Product */
     foreach ($collection as $item) {
         $sampleData[] = $item->getName();
     }
 
     return $sampleData;
 }

 

A látható termékek közül véletlenszerűen kiválaszt ötöt és ezek neveit egy tömbként adja át.

 

A sample-sidebar.js fájlban inicializált többi függvény egyszerű példa adatokat hoz létre, hogy a HTML template fájlban ezeken végig iterálva segítse a működés megértését. A második példa block-unk mindezek után így jelenik meg a forntend-en:

 

magento 2 modul fejlesztés frontend

 

ÖsszegzésA cikkben megpróbáltuk bemutatni a Knockout JS használatát egyedi modul fejlesztéshez. A Magento 2.0-ban nagyon sok helyen van alkalmazva customer, order, cart, wishlist és egyéb adatok megjelenítése céljából a frontenden.

 

69 válaszok
  1. best CBD oil says:

    I think this is one of the most significant info for me.
    And i’m glad reading your article. But wanna remark on some general things, The web site
    style is wonderful, the articles is really great : D.

    Good job, cheers

  2. carte prepagate says:

    Appreciating the commitment you put into your website and in depth information you provide.

    It’s nice to come across a blog every once in a while that
    isn’t the same old rehashed information. Excellent
    read! I’ve bookmarked your site and I’m adding your RSS feeds to my Google account.

  3. Royal CBD says:

    Heya this is kinda of off topic but I was wondering if
    blogs use WYSIWYG editors or if you have to manually
    code with HTML. I’m starting a blog soon but have no coding expertise so I wanted to get advice from someone with experience.
    Any help would be enormously appreciated!

  4. best CBD oil says:

    Tremendous things here. I’m very happy to peer your
    post. Thanks so much and I am taking a look forward to contact
    you. Will you kindly drop me a mail?

  5. buy CBD oil says:

    Hey there would you mind sharing which blog platform you’re working with?
    I’m going to start my own blog in the near future but I’m
    having a hard time deciding between BlogEngine/Wordpress/B2evolution and Drupal.
    The reason I ask is because your layout seems different then most blogs
    and I’m looking for something completely unique.

    P.S My apologies for being off-topic but I had to ask!

  6. best CBD cream for pain says:

    My spouse and I stumbled over here coming from a
    different web page and thought I might as well check things out.
    I like what I see so now i’m following you. Look forward to going over your web page yet
    again.

  7. CBD salve says:

    Hey! Do you know if they make any plugins to protect against hackers?
    I’m kinda paranoid about losing everything I’ve worked hard on. Any recommendations?

  8. full spectrum CBD oil says:

    Excellent goods from you, man. I’ve take into accout your stuff prior to and you are simply too great.
    I really like what you have acquired right here, really like what you’re stating and the way in which through which you say it.
    You make it enjoyable and you continue to care for to stay it sensible.
    I cant wait to learn far more from you. This is actually a wonderful site.

  9. Royal CBD says:

    I am curious to find out what blog system you happen to
    be utilizing? I’m having some small security problems with my latest
    website and I’d like to find something more safeguarded.

    Do you have any recommendations?

  10. Royal CBD says:

    Greetings! I know this is kind of off topic but I was
    wondering which blog platform are you using for this website?

    I’m getting fed up of WordPress because I’ve had issues with hackers and I’m
    looking at options for another platform. I would be fantastic if
    you could point me in the direction of a good platform.

  11. best CBD capsules says:

    whoah this blog is wonderful i really like reading your articles.
    Keep up the great work! You realize, lots of individuals
    are hunting round for this information, you can help them greatly.

  12. CBD products says:

    hello!,I love your writing so much! share we keep in touch extra about your article on AOL?

    I need a specialist in this area to unravel my problem.
    Maybe that’s you! Looking ahead to see you.

  13. Royal CBD topical says:

    Wow that was strange. I just wrote an very long
    comment but after I clicked submit my comment didn’t show up.
    Grrrr… well I’m not writing all that over again. Regardless, just
    wanted to say superb blog!

  14. Royal CBD gummies 10mg says:

    I am not sure where you are getting your info, but good topic.
    I needs to spend some time learning much more or understanding more.

    Thanks for magnificent information I was looking for this information for my mission.

  15. buy CBD says:

    Thank you a bunch for sharing this with all people you
    actually recognize what you are talking approximately!
    Bookmarked. Please additionally seek advice from my site =).

    We may have a hyperlink change contract among us

  16. Royal CBD pills says:

    Unquestionably consider that that you said. Your favourite reason appeared to be at the web the simplest factor to understand of.
    I say to you, I definitely get annoyed at the same time as folks consider worries that they
    just do not recognise about. You managed to hit the nail upon the
    top and also outlined out the entire thing without having side effect ,
    people could take a signal. Will likely be again to get more.
    Thanks

  17. buy CBD oil says:

    Today, while I was at work, my cousin stole my iphone and tested
    to see if it can survive a 40 foot drop, just so
    she can be a youtube sensation. My iPad is now broken and she has 83 views.
    I know this is entirely off topic but I had to share it with someone!

  18. buy CBD oil says:

    Magnificent beat ! I wish to apprentice at the same
    time as you amend your website, how could i subscribe for a blog web site?
    The account helped me a acceptable deal. I have been tiny bit familiar of this your broadcast
    provided brilliant clear concept

  19. CBD cream says:

    This is a very good tip especially to those new to the blogosphere.
    Short but very precise info… Thank you for sharing this one.
    A must read post!

  20. CBD gummies says:

    I feel this is one of the such a lot significant information for me.
    And i am happy studying your article. But wanna statement on some common things, The site style is
    perfect, the articles is in point of fact nice : D. Excellent task, cheers

  21. CBD gummies says:

    Really when someone doesn’t be aware of after that its
    up to other visitors that they will assist, so here it takes place.

  22. CBD gummies for anxiety says:

    I’m impressed, I have to admit. Rarely do I encounter a blog that’s both equally educative and
    engaging, and let me tell you, you’ve hit the nail on the head.
    The issue is something that too few folks are
    speaking intelligently about. I’m very happy I found this in my
    search for something relating to this.

  23. CBD gummies says:

    Very great post. I just stumbled upon your weblog and
    wanted to say that I have truly loved surfing around your weblog posts.
    In any case I will be subscribing in your feed and I’m hoping you write once more
    very soon!

  24. best CBD oil says:

    Everything is very open with a really clear explanation of the challenges.
    It was really informative. Your website is very useful.

    Thank you for sharing!

  25. best CBD pills says:

    Hello my friend! I want to say that this article is amazing, nice written and come with almost all
    vital infos. I’d like to see extra posts like this .

  26. best cbd cream for pain says:

    Hello! I’m at work surfing around your blog from my new iphone!
    Just wanted to say I love reading through your blog and look forward to all your posts!

    Keep up the great work!

  27. best CBD gummies says:

    I have been surfing online more than 3 hours nowadays,
    but I never found any interesting article like yours.
    It is pretty value enough for me. Personally, if all website owners and
    bloggers made just right content as you did, the
    internet shall be a lot more useful than ever before.

  28. sim gia goc says:

    Simply desire to say your article is as astonishing.
    The clearness in your post is just nice and i can assume you’re
    an expert on this subject. Fine with your permission allow me
    to grab your feed to keep updated with forthcoming post.
    Thanks a million and please continue the enjoyable work.

  29. big spoon for sale says:

    Generally I don’t learn article on blogs, however I wish to
    say that this write-up very forced me to check out and do it!
    Your writing style has been surprised me. Thank you, quite nice
    post.

  30. buy cialis says:

    Afterward I earlier left field a gossip I seem to undergo clicked the
    -Apprize me when raw comments are added- checkbox and directly whenever a gloss
    is added I suffer quadruplet emails with the take Saame remark.

    On that point has to be a agency you arse slay me from that service?
    Congratulations! http://www.cialisles.com/

  31. CBD says:

    My brother recommended I might like this website.
    He was entirely right. This post truly made my day.
    You cann’t imagine just how much time I had spent for this information! Thanks!

    Look into my site: CBD

  32. CBD products says:

    Simply wish to say your article is as astonishing.
    The clearness for your put up is just nice and that
    i can assume you’re a professional on this subject.
    Well along with your permission let me to snatch your feed to stay up to date with imminent
    post. Thank you 1,000,000 and please carry on the rewarding work.

    Feel free to visit my webpage – CBD products

  33. CBD oils UK says:

    Nice blog! Is your theme custom made or did you download it from somewhere?
    A design like yours with a few simple tweeks would really make my blog jump
    out. Please let me know where you got your theme. Kudos

    Look at my website … CBD oils UK

  34. CBD oil UK says:

    Right here is the perfect webpage for anyone who would like to find out about this
    topic. You understand so much its almost hard to argue with you (not that I really would want to…HaHa).
    You definitely put a brand new spin on a subject
    that has been discussed for ages. Wonderful stuff, just wonderful!

    Here is my web page; CBD oil UK

  35. CBD oil says:

    Hello, i believe that i saw you visited my website so
    i came to return the favor?.I am trying to find issues to enhance my website!I suppose
    its good enough to make use of some of your ideas!!

    Also visit my page: CBD oil

  36. best CBD oils UK says:

    I am extremely impressed with your writing skills
    as well as with the layout on your blog. Is this a paid theme or
    did you modify it yourself? Anyway keep up the excellent quality writing, it’s rare to see
    a great blog like this one today.

    Stop by my page … best CBD oils UK

  37. Cassandra Callens says:

    El listado de localidades cubiertas por los servicios de reparación de frigoríficos Iberna puede consultarlo en el panel derecho de nuestra página con el título de Reparación en Madrid”. Queda igualmente informado de la posibilidad de ejercitar los derechos de acceso, rectificación, cancelación y oposición, de sus datos personales en Vivero de Empresas C/ Villablanca, 85 28032 Madrid. Nuestros técnicos sabrán que hacer, estamos en los principales centros comerciales de Barcelona! COM Infórmate de nuestros servicios además de las noticias y acceso a ofertas exclusivas!

  38. this site says:

    Hey there! I just wanted to ask if you ever have any problems with hackers?

    My last blog (wordpress) was hacked and I ended up losing months
    of hard work due to no data backup. Do you have any solutions to stop hackers?

    my web site – this site

Trackbacks & Pingbacks

  1. hydroxychloroquine india

    Magento 2.0 modul fejlesztés lépésről lépésre – 4. rész (Knockout JS) – aionhills.com

  2. costco tylenol 500 mg

    Magento 2.0 modul fejlesztés lépésről lépésre – 4. rész (Knockout JS) – aionhills.com

  3. naltrexone szerint:

    naltrexone

    Magento 2.0 modul fejlesztés lépésről lépésre – 4. rész (Knockout JS) – aionhills.com

Hagyjon egy választ

Want to join the discussion?
Feel free to contribute!

Vélemény, hozzászólás?

Az email címet nem tesszük közzé.