Quantcast
Channel: Magento – Inchoo
Viewing all 263 articles
Browse latest View live

Using PostCSS with Sass

$
0
0

In this article we’ll be looking to a basic overview of PostCSS from the perspective of a developer whose current CSS development process includes use of CSS preprocessor, or in particular, Sass. If you’re a Sass user, there are a couple of approaches when starting out with PostCSS. You could make a complete switch and recreate your basic preprocessing environment with PostCSS, or you could start using it as a supplement.

Many of you will say that you still only rely on your favorite preprocessor, but then, it’s possible that you’re also using Autoprefixer for vendor prefixing, and guess what? In this case, you have already included PostCSS into your workflow.

What exactly are we talking about?

PostCSS is a tool, or basically, just an API which handles its plugins written in JavaScript.

Comparing to Sass, which has a bunch of features out of the box, PostCSS comes as a blank plate, ready to be filled with the ingredients you need.

Basic Setup

Including PostCSS into your project is not a complicated process, especially if you have a basic experience of using some of the task runners, such as Gulp or Grunt.

As a simple example, let’s take a look at the following gulpfile.js.

var gulp = require('gulp'),
    postcss = require('gulp-postcss'),
    autoprefixer = require('autoprefixer');
 
gulp.task('css', function() {
  return gulp.src('src/style.css')
    .pipe(postcss(
      autoprefixer()
    ))
    .pipe(gulp.dest('dest/style.css'));
});

What we see here is a two step process:

  1. First, we include the main PostCSS module.
  2. Next, we add PostCSS plugin(s) we want to use (which in these short example is the only one – Autoprefixer).

Of course, like with any new gulp plugin which you include into your gulpfile.js, PostCSS module and any additional PostCSS plugin need to be installed first. This can be done in a terminal, with a simple command, familiar to all Gulp users:

npm install gulp-postcss autoprefixer --save-dev

Choosing plugins

So, which plugins do we need? Well, this comes to your individual choice. For an easy start or just for supplementing your preprocessing workflow with some additional power, you will certainly gain an instant benefit with these two:

  • Autoprefixer – probably the most popular PostCSS plugin, used for adding required vendor prefixes. As already mentioned at the beginning, there is high chance that you’re already using this one.
.box {
  display: flex;
}
 
// Result after processing
.box {
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
}
  • Stylelint – a linting plugin useful for maintaining consistent conventions and avoiding errors in your stylesheets.
  • If you want to get in more deeper and recreate your basic Sass environment, most likely you’ll also need to require the following plugins:

    $blue: #056ef0;
    $column: 200px;
     
    .menu_link {
        background: $blue;
        width: $column;
    }
     
    // Result after processing
    .menu_link {
        background: #056ef0;
        width: 200px;
    }
      • Postcss-nested – gives us a functionality of unwrapping nested rules like how Sass does it.
    .phone {
        &_title {
            width: 500px;
            @media (max-width: 500px) {
                width: auto;
            }
        }
    }
     
    // Result after processing
    .phone_title {
        width: 500px;
    }
    @media (max-width: 500px) {
        .phone_title {
            width: auto;
        }
    }
    @define-mixin icon $network, $color: blue {
        .icon.is-$(network) {
            color: $color;
            @mixin-content;
        }
        .icon.is-$(network):hover {
            color: white;
            background: $color;
        }
    }
     
    @mixin icon twitter {
        background: url(twt.png);
    }
    @mixin icon youtube, red {
        background: url(youtube.png);
    }
     
    // Result after processing
    .icon.is-twitter {
        color: blue;
        background: url(twt.png);
    }
    .icon.is-twitter:hover {
        color: white;
        background: blue;
    }
    .icon.is-youtube {
        color: red;
        background: url(youtube.png);
    }
    .icon.is-youtube:hover {
        color: white;
        background: red;
    }

    One of the most interesting plugins that we’re mentioning last, is CSSNext. This is actually a collection of plugins that, together, give us a possibility to use the latest CSS syntax today. It transforms new CSS specs into more compatible CSS without a need to waiting for browser support. CSSNext has a lot of features and some of them are:

    • custom properties set & @apply
    • custom properties & var()
    • custom selectors
    • color() function
    • :any-link pseudo-class, etc.

    In your CSS file you can do something like this:

    // Example for custom properties set & @apply
    :root {
      --danger-theme: {
        color: white;
        background-color: red;
      };
    }
     
    .danger {
      @apply --danger-theme;
    }

    Why should you use PostCSS?

    So, if you already have an effective workflow and you’re satisfied with using your favorite preprocessor for some time now, you might be still asking yourself why do I need to learn another tool (or make the switch from Sass)? What are the benefits?

    To answer these questions, let’s summarize some of the advantages:

    • Speed – even though in the meantime Sass got a significantly faster (e.g., LibSass), PostCSS is still the winner here
    • Modularity – reduces bloat; you only include the functionality that you need
    • Lightweight – with previous benefit, you get also this one
    • Immediate implementation – if you want a new functionality, you don’t have to wait for Sass to be updated; you can make it on your own

    Of course, everything’s not ideal and there are also certain drawbacks:

    • Increased complexity – more planning is required (e.g., plugins must be called in a specific order)
    • A different syntax (compared to Sass)
    • PostCSS processing requires valid CSS

    What’s next

    It’s perfectly clear that PostCSS is all about the plugins. At the time of writing, there are more than 200 plugins available (and this number is only getting bigger). So, to go beyond the basics, you’ll need to search for other plugins that will extend this barebones setup.

    Of course, if you find out that some handy functionality is missing, go ahead and solve the problem by making your own PostCSS plugin.

    The post Using PostCSS with Sass appeared first on Inchoo.


    How I learned to stop worrying and love 3rd party modules for Magento

    $
    0
    0

    It’s just another day at the office. “Coffee tastes best on a Friday morning”, you think to yourself while reading emails from the previous day. Couple of guys are shouting in the back, and you’re shushing them angrily. With noise slowly diminishing, you find yourself checking an email with a request for custom change on one of your Magento stores. “Guys, we’ve got work to do. WeHeart3rdPartyModules Ltd. just requested a custom change on one of their stores. Who is willing to take on this one?”. Silence takes over the room. The only sound you hear is a fan from your colleagues PC (no need to ask why, Magento 2 is deploying static content). It seems like you’re in this alone. “Damn, I was hoping for a slow Friday”.

    While PHPStorm is loading up, you’re reading the requirements:

    Hi guys,

    we would like to add a new column to our sales orders grid. We understand we have 20 of them already, but we really need this one – coupon description. Can you do this for us?

    Thanks


    “You mean rule description, not coupon, noob!”
    , a smirk appears on your face. “I’ve done this a couple of times already. Also, I think we have an article written about this exact problem”.

    Using your google-fu, an article pops up on the screen: How to extend Magento Order Grid? says the title. “Just what I need. This will be a piece of cake”.

    IDE is finally loaded, and you’re rushing to get this done as fast as possible. “app, code, local, Inchoo” – your mind shouts – “you’ve done this a million times before” – but then you stop, and dark cloud just hangs over you. “Now I remember why there were no volunteers. This project has over a hundred 3rd party modules in local pool alone”. Not only your Friday is ruined, but the entire weekend does not look promising either.

    “Well, let’s dig in, then.” Your train of though is something along the lines: “they have over 100 modules.. email mentions some earlier changes on sales order grid (having 20 columns already).. there must be a 3rd party module enabling them, or.. have we implemented this change already?”. You jump to Inchoo_WeHeart module’s config.xml and see no rewrites on 'sales_order_grid' block. It must be 3rd party then. Lovely.

    Fortunately, your IDE is good in identifying class hierarchy, and in just a couple of clicks you find the class rewrite, and 3rd party module responsible (actually several of them, but two are disabled):  Amasty extended order grid. This should be fun. Module allows us to select columns to add to “Sales > Orders” grid. Adrenaline spikes up – “what if the column we need can be added via admin? That would be so great”. You check system.xml and adminhtml.xml, run to the admin and start looking for columns. “Of course it’s not possible, it was too good to be true”.

    You’ve been in this situation before, and you do the usual routine: start randomly opening 3rd party module’s classes hoping to find something so obvious that would allow you to finish this as fast as possible. Of course, you fail. Looks like a more systematic approach is needed after all. You’re checking config.xml, opening up observers, helpers, and many other classes, tracing the code. Finally you find it. It’s so obvious now.

    Now, off to coupon description field. You go through Magento database, and find that coupon description is located in 'salesrule_coupon' table, but there is no description. Description is in another table – 'salesrule'. Looks like we’ll need 2 SQL joins for this. “It’s just a few lines”, a voice in your head says, “no one will notice it. And it’s not a core code, right?”. After a short struggle, you resist the temptation and decide not to change the module’s code directly (sell your soul for a quick fix).

    Copying the code to local pool (if the module is in community) is also an option, but we can, and should, do better than that. Is class rewrite good enough? In most situations, yes, and this is one of those situations too. You quickly whip up a rewrite: “I can do this in my sleep”, you think to yourself, but somehow manage to spend 45 minutes on getting it to work (damn XML typo).

    <?php
     
    class Inchoo_WeHeart_Helper_Ogrid_Columns extends Amasty_Ogrid_Helper_Columns
    {
     
        function prepareOrderCollectionJoins(&$collection, $orderItemsColumns = array()){
     
            parent::prepareOrderCollectionJoins($collection, $orderItemsColumns);
     
            $collection->getSelect()->joinLeft(
                array(
                    'salesrule_coupon' => $collection->getTable('salesrule/coupon')
                ),
                'order.coupon_code = salesrule_coupon.code',
                array('salesrule_coupon.rule_id as coupon_rule_id')
            )->joinLeft(
                array(
                    'salesrule_rule' => $collection->getTable('salesrule/rule')
                ),
                'salesrule_rule.rule_id = salesrule_coupon.rule_id',
                array('salesrule_rule.description as coupon_description')
            );
     
        }
     
        function getDefaultFields(){
     
            if (!$this->_defaultField){
                $this->_defaultField = array_merge(parent::getDefaultFields(), array(
                    'discount_description' => array(
                        'header' => Mage::helper('sales')->__('Discount Description'),
                        'index' => 'coupon_description',
                        'filter_index' => 'salesrule_rule.description'
                    )
                ));
            }
     
            return $this->_defaultField;
        }
     
    }

    You look satisfied with the solution. Module has been changed, but in an unobtrusive way. Original logic has been kept, and new functionality added. With a few keystrokes, code is committed to a feature branch, and pushed to dev server. You test it there once more, and it looks like it’s working. Response message is sent:

    “Hi WeHeart”, (no need to call them by their full name, they’re used to it), “changes have been made and pushed to dev server for you to test. Regards, Inchoo.

    You close down the browser, and check the time: “Oh, looks like it’s time for my break”.

    The post How I learned to stop worrying and love 3rd party modules for Magento appeared first on Inchoo.

    Wireframing a successful design for your online store

    $
    0
    0

    As designers, we’re often faced with a lot of questions about our process. We never just dive into design and bask in the glory of amazing typography and brilliant color schemes because without the phases that precede it – it just wouldn’t even begin to be possible.

    Beam me up, designer

    The worst feedback you can give to a designer is commenting how pretty the design is. We don’t aim for pretty, can’t learn from it and our clients aren’t always satisfied with just pretty. In the end, if our clients’ customers don’t see the use in the pretty that we’ve created – the webshop will fail at some point.

    Our process includes mechanisms that minimise that risk and allow us to make informed decisions for a successful webshop. Before the actual design phase, we go through planning and wireframing.

    At this point we answer some of the questions that even we can’t answer just by being designers. We have best practice knowledge and experience, but we need that custom touch. If we can take a sneak peek at your Google Analytics or Hotjar, we can answer everything from what resolutions we should be making our designs on (according to most visits on certain devices) to how descriptive we should make our labels (if the age group of your returning customers is a bit older).

    Depending on if the project budget allows us, we’ll sit through Hotjar recordings to find out where the hiccups are on your current site and what user flows are actually not flowing so well. Our redesign or incremental changes need to perform better than that. The work becomes directed and concentrated like a tractor beam on a star ship. We want to pull your customers in, and do so pain free.

    All work, some play

    Along with all that analysing, we communicate with our clients – a lot.

    We sit at our table a lot, drink coffee too much, headphones in ears – we look like we’re doing great in our own little world. The truth is there is always some communication going on. So. Much. Communication. Especially in the wireframing phase where a lot of relevant work gets done.

    That’s great though – in some ways we, as designers, feel like the glue that keeps the project connected. We estimate the size of the project, provide input on what could work for the online store (through user flows and usability best practices) and we even get to draw it up. We get to experiment on features and be the ones to present it to the clients. Our wireframes, design layouts and quick prototypes are much more budget-friendly than actual implementation. All the while talking to frontend, backend, fellow design colleagues and of course – our clients.

    There is a lot of dread amongst designers when it comes to client work and communication, but, we don’t think of this communication as trying to fight our client on every argument. Your client knows his business better than you, he knows the direction he wants to take it in – you need him just as much as he needs you, so, talk it out.

    Say your client can’t visualise a wishlist flow or he doesn’t quite see how animating tiles on tablet will save vertical space and leave more room for products – wireframe it, prototype it, present it, resolve the issue.

                             

    It’s not all black&white

    This entire article went by without mentioning the latest design trends, without complaining how hard it is for us to get the exact design we have in our heads approved and how we’re missing proper tools to correspond to responsive demands.

    The least of our problems is opening our tools and creating the design – it has its own challenges, but that’s on myself and my skills. It’s everything else – the planning, knowing the technology, the communication – that ends up being some of the hardest work and our best efforts in creating meaningful experiences and a successful online shop.

    The post Wireframing a successful design for your online store appeared first on Inchoo.

    Making FedEx api show shipping estimate

    $
    0
    0

    There always comes the time when shopkeeper decides that he want’s to inform his customer of shipping estimate on checkout, so they could know approximately when they will get their goods. And for that, many shops today rely on API-s like ones from USPS or FedEx. Both of which are available for Magento.

    In this article I will be showing you how to override FedEx carrier to return shipping estimate for given rates.

    Overriding carrier

    FedEx carrier works by sending request with given flags to FedEx server, who then, based on given flags, prepares response. After Magento receives response, it parses each rate as “Mage_Shipping_Model_Rate_Result” which he will later pass on to “Mage_Sales_Model_Quote_Address_Rate”. From which we will be able to access in template to show in frontend. For that, we will first override “Mage_Usa_Model_Shipping_Carrier_Fedex”.

    Adding a flag for api

    For FedEx API to know that it needs to return shipping estimate it needs to be given a “ReturnTransitAndCommit” flag. If we look into “Mage_Usa_Model_Shipping_Carrier_Fedex::_formRateRequest()” method, we will see that all it does is prepare flags into an array before it is sent to be parsed into request.

    All we need to do here is rewrite original method and at the end of the array add our own flag.

    Like in given example:

    public function _formRateRequest($purpose)
    {
       $ratesRequest = parent::_formRateRequest($purpose);
       $ratesRequest['ReturnTransitAndCommit'] = true ;//Here we are adding flag
       return $ratesRequest;
    }

    Storing the response data

    After we receive data from api it will be parsed into stdClass,and in that class we are only interested in ‘RateReplyDetails’ array, which holds all our rates and their details, including their shipping estimates.

    What we need to do here is pass our shipping estimate data into rate that will be passed on. For that we will be rewriting “Mage_Usa_Model_Shipping_Carrier_Fedex::_prepareRateResponse()” method.

    protected function _prepareRateResponse($response)
    {
       $costArr = array();
       $priceArr = array();
       // Array in which to store timestamp
       $deliveryTimeStamp = array();         //
       $errorTitle = 'Unable to retrieve tracking';
     
       if (is_object($response)) {
           if ($response->HighestSeverity == 'FAILURE' || $response->HighestSeverity == 'ERROR') {
               if (is_array($response->Notifications)) {
                   $notification = array_pop($response->Notifications);
                   $errorTitle = (string)$notification->Message;
               } else {
                   $errorTitle = (string)$response->Notifications->Message;
               }
           } elseif (isset($response->RateReplyDetails)) {
               $allowedMethods = explode(",", $this->getConfigData('allowed_methods'));
     
               if (is_array($response->RateReplyDetails)) {
                   foreach ($response->RateReplyDetails as $rate) {
                       $serviceName = (string)$rate->ServiceType;
                       if (in_array($serviceName, $allowedMethods)) {
                           $amount = $this->_getRateAmountOriginBased($rate);
                           $costArr[$serviceName]  = $amount;
                           $priceArr[$serviceName] = $this->getMethodPrice($amount, $serviceName);
                           //Store timestamp into prepared array
                           $deliveryTimeStamp[$serviceName] = $rate->DeliveryTimestamp; //
                       }
                   }
                   asort($priceArr);
               } else {
                   $rate = $response->RateReplyDetails;
                   $serviceName = (string)$rate->ServiceType;
                   if (in_array($serviceName, $allowedMethods)) {
                       $amount = $this->_getRateAmountOriginBased($rate);
                       $costArr[$serviceName]  = $amount;
                       $priceArr[$serviceName] = $this->getMethodPrice($amount, $serviceName);
                   }
               }
           }
       }
     
       $result = Mage::getModel('shipping/rate_result');
       if (empty($priceArr)) {
           $error = Mage::getModel('shipping/rate_result_error');
           $error->setCarrier($this->_code);
           $error->setCarrierTitle($this->getConfigData('title'));
           $error->setErrorMessage($errorTitle);
           $error->setErrorMessage($this->getConfigData('specificerrmsg'));
           $result->append($error);
       } else {
           foreach ($priceArr as $method=>$price) {
               $rate = Mage::getModel('shipping/rate_result_method');
               $rate->setCarrier($this->_code);
               $rate->setCarrierTitle($this->getConfigData('title'));
               $rate->setMethod($method);
               $rate->setMethodTitle($this->getCode('method', $method));
               $rate->setCost($costArr[$method]);
               $rate->setPrice($price);
    	   //Store timestamp into rate
               $rate->setDeliveryTimeStamp($deliveryTimeStamp[$method]); //
               $result->append($rate);
           }
       }
       return $result;
    }

    Almost done..

    Before we start celebrating we will notice that we are still not getting that data in fronted. That is because we stored that data into “Mage_Shipping_Model_Rate_Result_Method” which is is not same type of rate as in template, where we have “Mage_Sales_Model_Quote_Address_Rate”. So we will additionally rewrite method “Mage_Sales_Quote_Address_Rate::importShippingRate()” and just pass our data whether its present or not.

    public function importShippingRate(Mage_Shipping_Model_Rate_Result_Abstract $rate)
    {
       parent::importShippingRate($rate);
       if ($rate instanceof Mage_Shipping_Model_Rate_Result_Method) {
           $this->setDeliveryTimeStamp($rate->getDeliveryTimeStamp());
       }
       return $this;
    }

    And we are done. Wherever you call for rates (in my case it was checkout/onepage/shipping_method/available.phtml – onepages shipping methods) fedEx api will provide shipping estimate.

    Thanks for being with me and happy coding.

    The post Making FedEx api show shipping estimate appeared first on Inchoo.

    Experiences of running Magento 1 on PHP 7

    $
    0
    0

    How time flies! It’s been almost a year and a half since we released Inchoo_PHP7 extension for Magento 1 (https://github.com/Inchoo/Inchoo_PHP7), and announced that in a blog post (http://inchoo.net/magento/its-alive/).

    We were just scratching our own itch – wanting to use all the performance benefits PHP 7 brought to run Magento faster. But, as the cover image of that post inadvertently prophesied, we created a monster that escaped, and is continuing to terrorize the villagers to present day.

    So, what happened in the meantime?

    • M1 is still going strong. M2 will take over eventually, but, in my humble personal opinion, that’s going be a very slow process.
    • On the other hand, PHP 7 is overtaking PHP 5 much quicker than previous versions used to replace their predecessors. (https://seld.be/notes/php-versions-stats-2016-2-edition). This is logical, because it really brings huge performance improvements, and it really is quite compatible, considering the major version number change.
    • Magento core became PHP 5.6 compatible in 1.9.3.x. Inchoo_PHP7 became PHP 7.1 compatible.
      • But, believe it or not, my humble personal opinion is that it’s better to run Magento on PHP 7.0 than 7.1 (https://github.com/Inchoo/Inchoo_PHP7/wiki/RunningOnPHP7.1).
        It’s really difficult to say, but I guess there are hundreds of M1 sites powered by Inchoo_PHP. Just Inchoo alone built from scratch or upgraded double figure of sites to PHP 7. Community seems to be going strong with it too, so I think I can say that it is quite tried and true by now.
    • With help from community, we found and fixed a lot of edge cases, and can say quite comfortably that we can make pretty much any M1 site work on PHP 7 without a problem.
      • And, in the last release, we even created a testing shell script, which can be very useful to find and fix potential problems.
      • Just keep in mind that this still is, and always will be, a developer-level extension. It can be “install and forget” if you are lucky, but, it would be good to have someone knowledgeable to set up, test everything and fix any issues. We had clients who came to us just for this, and we were always able to help them upgrade to PHP 7. And I don’t even want to say you need Inchoo to set it up. There are a number of developers and agencies in the Magento community that can help you out.

    What’s ahead?

    • Looks like M1 core will continue it’s evolution. Going to Composer install, PHP 7, full page cache on CE, etc. – these are all things that clients are demanding and Magento experts are able to provide. Whether someone likes it or not, the whole ecosystem is large enough to be able to live and evolve on its own, directed by market pressures, and not someone’s will.
    • 3rd party extensions are the part of code we can’t fix with our extension. So, whether you are a 3rd party extension creator, a client, an integrator or anywhere else in community, please help spread the awareness that it’s good for everyone to be PHP 7 compatible.
    • PHP 7.2 will remove Mcrypt extension, and M1 core is quite dependant on it. There are a few workarounds we already tried out, but it’s not going to be pretty. For the time being, stick to PHP 7.0, and you won’t have problems.
    • Personally, I can’t wait for the moment when I’ll be able to use PHP 7 features when programming Magento, not just it’s performance. Stronger typing, for one, should bring less bugs and a more secure system. But that is still in far future from now, unfortunately.

     

    TL;DR;

    M1 runs (much) faster on PHP 7. Quite easy to set up due to a 3rd party module Inchoo_PHP7. MIT license. Great community support.

    If you are having issues with performance of your Magento store, feel free to contact us to check technical state of your shop!

    See you around!

    The post Experiences of running Magento 1 on PHP 7 appeared first on Inchoo.

    Javascript Bundling in Magento 2

    $
    0
    0

    Javascript bundling is a technique that groups separate files in order to reduce the number of HTTP requests that are required to load a page. Bundling is commonly used in today’s “module-based” development where some functionalities are basically split into Modules (roughly explained). For loading modules, we usually use some of popular module loaders such as rollup.js or RequireJS (which is Magento’s weapon of choice).

    HTTP2 is here so this technique will be probably deprecated in the future. You could still use bundling for reducing the number of requests for a specific page but I don’t think it will be worth of it.
    Right before we start with “reverse” optimisation, we can use bundling to help us organise our assets and serve less files to the client which will (should) result in faster website.

    One of our services is Technical audit which covers various tests on both Frontend and Backend part of the client’s store. Since i’m Frontend Developer, performance is my key point during analysis.

    First thing I check is wether Javascript and CSS files are merged/minified/bundled (hello bundle). When I was Junior Developer, I was so happy/angry when I discovered someone didn’t turn these ON since I was thinking that technical level of Developers who worked on site was low and we can do a lot to improve site… and of course, I could get a “quick win” with turning merging/minifying ON.

    Well, in theory that was great. Usually I was right about their expertise level but I was wrong about one thing – they didn’t “forgot” to turn these ON.

    Explaining to the client that you would probably have to refactor 80% of the code to change that “simple option” is something I wouldn’t want to go through again.

    We were doing Technical audit of the Magento 2 store and when I found out that Javascript and CSS files were not merged/minified/bundled I was in shock and first question that was going through my head was “shall I report this to the client?!”.

    Strange question, I know. But it does make sense because if I report it, I’ll have to fix it.

    Just kidding, of course this was included in our report and was first thing I changed when we took over the project.
    How did it go? Well, do you write articles about stuff that work as expected?

    Magento 2 Bundling

    I must note that official docs do not give us a lot of information and there are only few discussions online about it.
    Therefore, I may be wrong in some of my conclusions so feel free to correct me in comments 🙂

    For start, let’s see 2 proper ways to load assets in Magento 2.
    In this example, you can see how assets are loaded on two different Magento pages – Homepage and product page.

    As you can see on example above, requireJS loaded different assets through different pages while all bundles were loaded regardless of wether they are needed or not.
    With RequireJS, we can load specific JS modules on specific pages which means you will load only necessarily assets and reduce number of requests.

    (If you wish to know more about how to load Javascript files with RequireJS, you can read this excelent article from my coleague Domagoj Potkoc. )

    While RequireJS did help us with reducing number of requests through the page, we still have few JS files being loaded and “few” in Magento 2 means 40-50. We need a way to merge those assets into just few files. We need to create 5 files out of 50.
    Files are being loaded asynchronously but still, if we could merge these files into just few, we could improve performance even more.
    Bundling comes as a problem solver since it is used to merge modules and it dependencies into single file.
    Bundling denies main benefit of using module loaders such as RequireJS since assets aren’t loaded asynchronously. Bundles are included with <script> tag, inside <head> section of the page.

    So, why use it?

    With bundling we could decide on where to load specific bundle and that’s the best part of it! For example, we want to put all checkout-related stuff into one bundle and load it only on checkout page!

    Feeling happy?

    Well, Magento folks didn’t implement RequireJS optimizer which is required for ordering and organising module load across different pages. You can exclude some scripts from bundle but you CAN’T decide on bundle content for a specific page.

    So, why use it?

    With Merging, you can only merge Javascript files that are NOT being loaded through RequireJS. If you wish to “merge” JS modules, you will have to use bundling.

    You probably have so many questions in your head right now. So do I. And I’m still searching for the answers.

    Here is a random Zen image, just to chill you down.

    Bundling misses key features (imho) but you can still change few things in order to organize your bundles.

    Bundle size and exclude list

    In Vendor/Theme/etc/view.xml you can change bundle size and exclude some scripts from bundle.

    Default size for bundle is 1MB.
    Bundle size determines number of bundles that will be created. For example, if you have 4MB of script files and bundle size is set to 1MB, you will have 4 bundles created.

    If number is too low, you will probably have 10 and more small bundles which will block each other during rendering so be careful with this.
    Remember that bundles are not loaded asynchronously.

    We can also exclude certain scripts from bundles. They will be loaded with RequireJS when needed.
    Keep in mind that Luma and Blank themes have their own exclude lists and if you are not properly fallbacking and don’t have your own exclude list, bundles will be huge since all JS modules will be bundled, regardless of whether you need them or not.

    <exclude> handle takes care of files that should be excluded from bundle. As far Magento 2 is concerned – since we can’t decide on bundle content for each page, at least we can exclude assets that will not be required through the whole site, therefore bundles will consist only files that are required everywhere. As you can see in above example (Luma theme exclude list), jQuery assets are probably required everywhere so i don’t understand idea behind excluding these from bundles. Probably Magento folks wanted to leave most important stuff under RequireJS control.

    Activating bundling

    After we have configured our bundle size and exclude list, it is time that we turn bundling on and enjoy great performance impact.

    We can turn bundling on here: Stores > configuration > advanced >developer

    After bundling is ON, clear cache and switch to production mode (bundling will not work in “Developer” mode). Static files will be deployed and you will see your bundles being loaded on the Frontend.

    What about performance?

    We did a lot in order to reduce number of requests through the site. But, there is one small problem with performance.

    This is Homepage of the Luma theme.
    Testing was done on Magento 2.2.0 “dev” version, with following setting in Dev console (Chrome):

    Before turning bundling ON:

    • Number of JS requests : 137
    • Size: 2.0MB
    • Loadtime: 9.46sec

    With bundling turned ON:

    • Number of JS requests : 8
    • Size: 4.2MB
    • Loadtime: 20.12sec

    Take a look at size and load time?
    We did reduce the number of JavaScript files being loaded, but the total filesize of generated bundles is larger than the total filesize of all non-bundled JavaScript files on the frontend.
    Reason? With RequireJS you load only needed JS files on a specific page. Bundling merges all JS  assets and serves them on all pages.

    Conclusion

    I must say I am disappointed with bundling, especially with the fact that we don’t have RequireJS optimizer by default. Without it, whole idea behind bundling is missed.

    Pros:

    • Bundles multiple files into single file

    Cons:

    • negates benefits of using module loader
    • filesize of bundles is larger than all non-bundled javascript files size in total (per page)
    • you can’t define bundle content precisely
    • you can’t decide in which pages which bundle will be loaded

    I don’t see any reason we should use bundling right now.
    Bundling will make sense in the future if Magento folks create additional functionalities for us to use.
    We need configuration file where we will be able to decide on :

    • number of bundles
    • bundle size
    • bundle content
    • which pages specific bundle will be loaded

    Thanks for reading and i hope we can roll up some discussion about this topic in comments 🙂

    The post Javascript Bundling in Magento 2 appeared first on Inchoo.

    External database connection in Magento

    $
    0
    0

    Most of the time working with Magento, a single database connection is just enough. Magento has excellent system of adding new tables in database or extending existing ones. So, why would there be a need for an external database connection outside the Magento system? Well, one of the examples is data migration from another ecommerce system. In this article, a simple connection to external database is explained with CRUD (create, read, update, delete) examples.

    Configuration

    This external database connection is similarly defined as the Magento default one – in an XML configuration. The difference is that foreign connection is defined inside particular module’s XML configuration. It defines read and write adapters, setup and database credentials information. Foreign tables are defined in the same way as magento tables. They are under inchoo_foreignconnection_resource node so the model resource can be invoked later in the code. For demonstration purpose, there’s a frontend node in XML configuration that defines front name of the controller (fconn).

    <?xml version="1.0"?>
    <config>
        <modules>
            <Inchoo_ForeignConnection>
                <version>1.4.2</version>
            </Inchoo_ForeignConnection>
        </modules>
        <global>
            <models>
                <inchoo_foreignconnection>
                    <class>Inchoo_ForeignConnection_Model</class>
                    <resourceModel>inchoo_foreignconnection_resource</resourceModel>
                </inchoo_foreignconnection>
                <inchoo_foreignconnection_resource>
                    <class>Inchoo_ForeignConnection_Model_Resource</class>
                    <entities>
                        <product>
                            <table>product_description</table>
                        </product>
                    </entities>
                </inchoo_foreignconnection_resource>
            </models>
            <resources>
                <inchoo_foreignconnection_write>
                    <connection>
                        <use>inchoo_foreignconnection_database</use>
                    </connection>
                </inchoo_foreignconnection_write>
                <inchoo_foreignconnection_read>
                    <connection>
                        <use>inchoo_foreignconnection_database</use>
                    </connection>
                </inchoo_foreignconnection_read>
                <inchoo_foreignconnection_setup>
                    <connection>
                        <use>core_setup</use>
                    </connection>
                </inchoo_foreignconnection_setup>
                <inchoo_foreignconnection_database>
                    <connection>
                        <host><![CDATA[localhost]]></host>
                        <username><![CDATA[username]]></username>
                        <password><![CDATA[password]]></password>
                        <dbname><![CDATA[db_name]]></dbname>
                        <initStatements><![CDATA[SET NAMES utf8]]></initStatements>
                        <model><![CDATA[mysql4]]></model>
                        <type><![CDATA[pdo_mysql]]></type>
                        <pdo_type><![CDATA[]]></pdo_type>
                        <active>1</active>
                    </connection>
                </inchoo_foreignconnection_database>
            </resources>
        </global>
        <frontend>
            <routers>
                <inchoo_foreignconnection>
                    <use>standard</use>
                    <args>
                        <module>Inchoo_ForeignConnection</module>
                        <frontName>fconn</frontName>
                    </args>
                </inchoo_foreignconnection>
            </routers>
        </frontend>
    </config>

    Model

    Next thing is a model that will use defined foreign connection to get data or to save data in a foreign database. Here, the model is initialized with the product table from XML configuration, that in this case defines product_description table.

    class Inchoo_ForeignConnection_Model_Product extends Mage_Core_Model_Abstract
    {
        protected $_eventPrefix = 'inchoo_foreignconnection_product';
        protected $_eventObject = 'product';
     
        protected function _construct()
        {
            $this->_init('inchoo_foreignconnection/product');
        }
    }

    Model resource class is also defined with the same xml configuration node in _init() function, but with the TABLE_PRIMARY_KEY parameter. In this class, several functions can be created that will work with external data.

    First example is createDataInResource function, which inserts data in model’s table. It takes array of parameters that will be inserted.

    Second example is a readDataFromResource function that fetches all data from model’s table. Read adapter must be defined first. It is a configuration node from xml that defines read connection. After read adapter definition, Magento database functions can be used (select(), from(), limit(), etc..). When query is constructed completely, it can be executed with read adapter. Data can be retrieved with fetchPairs() or fetchAll() function. fetchAll() is used to get all records returned from mysql.

    updateDataInResource and deleteDataFromResource functions take additional $id parameter that defines which record will be updated or deleted.

    class Inchoo_ForeignConnection_Model_Resource_Product extends Mage_Core_Model_Resource_Db_Abstract
    {
        const TABLE_PRIMARY_KEY = 'product_id';
     
        protected function _construct()
        {
            $this->_init('inchoo_foreignconnection/product', self::TABLE_PRIMARY_KEY);
        }
     
        public function createDataInResource($values = array())
        {
            $writeAdapter = $this->_getWriteAdapter();
            try {
                $writeAdapter->insert(
                    $this->getMainTable(),
                    $values
                );
            } catch (Exception $e) {
                Mage::log('Unable to insert data to external resource. ' . $e->getMessage(), null, null, true);
            }
        }
     
        public function readDataFromResource()
        {
            $data = array();
            $readAdapter = $this->_getReadAdapter();
            $select = $readAdapter->select()
                ->from($this->getMainTable(), '*')
                ->limit(20);
     
            try {
                $data = $readAdapter->fetchAll($select);
            } catch (Exception $e) {
                Mage::log('Unable to fetch data from external resource. ' . $e->getMessage(), null, null, true);
            }
     
            return $data;
        }
     
        public function updateDataInResource($id, $values = array())
        {
            $writeAdapter = $this->_getWriteAdapter();
            try {
                $writeAdapter->update(
                    $this->getMainTable(),
                    $values,
                    self::TABLE_PRIMARY_KEY . '=' . $id
                );
            } catch (Exception $e) {
                Mage::log('Unable to update data in external resource. ' . $e->getMessage(), null, null, true);
            }
        }
     
        public function deleteDataFromResource($id)
        {
            $writeAdapter = $this->_getWriteAdapter();
            try {
                $writeAdapter->delete(
                    $this->getMainTable(),
                    self::TABLE_PRIMARY_KEY . '=' . $id
                );
            } catch (Exception $e) {
                Mage::log('Unable to delete data from external resource. ' . $e->getMessage(), null, null, true);
            }
        }
    }
    class Inchoo_ForeignConnection_Model_Resource_Product_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract
    {
        public function _construct()
        {
            $this->_init('inchoo_foreignconnection/product');
        }
    }

    Usage in controller

    All these functions are demonstrated in IndexController class but since they are defined in model’s resource class, they can be called in any controller class.

    class Inchoo_ForeignConnection_IndexController extends Mage_Core_Controller_Front_Action
    {
        public function indexAction()
        {
            // Create
            $foreignProductCreate = Mage::getModel('inchoo_foreignconnection/product')->getResource();
            $foreignProductCreate->createDataInResource(
                array(
                    'product_name' => 'Product name',
                    'product_description' => 'Product description'
                )
            );
     
            // Read
            $foreignProductRead = Mage::getModel('inchoo_foreignconnection/product')->getResource();
            $result = $foreignProductRead->readDataFromResource();
            var_dump($result);
     
            // Update
            $foreignProductUpdate = Mage::getModel('inchoo_foreignconnection/product')->getResource();
            $foreignProductUpdate->updateDataInResource(
                3394,
                array(
                    'product_name' => 'Product name updated',
                    'product_description' => 'Product description updated'
                )
            );
     
            // Delete
            $foreignProductDelete = Mage::getModel('inchoo_foreignconnection/product')->getResource();
            $foreignProductDelete->deleteDataFromResource(3394);
        }
    }

    In most scenarios, Magento will use different type of external connection to retrieve or send data, but sometimes an external database connection like this will be the best way to go. One of the examples would be when you want to import products from another system to Magento with their xsell or upsell products. In that case, read connection would be used to retrieve product data and write connection would be used to save xsell or upsell product ids in a temporary table so they can be assigned to Magento product when all products from external system are imported. 

    The post External database connection in Magento appeared first on Inchoo.

    Version control systems in UI design

    $
    0
    0

    Our design team switched to working in Sketch a while ago but it wasn’t until version 43 we really started seeing some opportunities to change our workflows more significantly. It was also an opportunity for more designers to work on the same project as well as collaborate with developers more easily.

    The case of version 43

    With Sketch version 43, Bohemian Coding quietly announced – a revised file format. Behold. They introduced a new file format that allows reading and writing .sketch files manually or programmatically and go from Sketch straight to implementation. What does that mean for our workflow? Sketch had a file format that was workable not only from the design side but developer side too.

    An unzipped Sketch file created .png preview and human-readable .json files. This created a question for us – could our design and development teams collaborate seamlessly? Are we finally to feed design assets directly to the development pipeline and integrate .sketch file at the same time as development?

    By having similar repositories we could both edit the same designs.  That could be any element, modular component or an entire screen UI. Anything visual can now go back and forth between Sketch and UI frontend seamlessly – in theory. What does that mean for us? We’d have to apply distributed version control to support this new, high-level way of collaborating not only within our design team but with developers outside the team as well to create a safe and responsible environment for project development. We also needed to find a way of knowing which one of us made changes, when they were made and if we can roll back to them. Seeing visually what these changes would be an added bonus.

    Just a side note, all of this also meant that documents created with Sketch 43 would not be compatible with previous versions. It was the quietest end of an era, perhaps for a reason.

    Talking to our backend developer and testing this new file format we found that this new Sketch format offers a different workflow, but it wasn’t the smoothest one. We weren’t quite convinced in the widely spread “Design is code, code is design” line. Sure, this brings different disciplines closer, but it doesn’t equate them. At least not yet. The whole rename .sketch extension to .zip, unzip it and then connect to GitHub (which we use) process does work (and you get to see the changes in the preview .png). But, for starters, it would be a lot easier if there was some sort of Sketch launcher that would eliminate the extension renaming process.

    It doesn’t seem possible to use branches in an effective way (they can’t be merged and all the integration work needs to be done manually) so we, designers can’t link our system with version control and as a consequence can’t have a good overview of the all the progress done on the design. Also, simply put, this is a great concept but we just don’t see a workflow where a developer might find himself changing design elements and checking that code back in so the Sketch app can render those changes next time we work on it.

    Aaron Weyenberg (UX lead from TED) best described when our workflows would actually improve. “It’ll be from Sketch’s open format leading to more powerful plugins and third party tools to make the day to day collaboration of designers and developers faster while not upending their existing habits.”

    Sharing is caring

    Recently we had the issue of multiple designers working on the same project. There were constant debates as to which file is the “freshest” and manual overriding to create one master file. It moved us to explore versioning systems we could use within our design team. Libraries, as we’ve seen with Craft, works well if you’re willing to create separate text styles, color and uncategorised layers that can then be used through multiple .sketch files.

    But, what if entire layouts and pages need to be changed and rearranged, not just styles and components. How do you update structure and flow?

    Symbols work great for single-player, single-file mode, libraries are more multi-player, multi-file kind of situation, but what about multi-player, single-file scenario? At what point and how do we unify our work and keep a neat overview of it?

    The tools of the trade

    Developers already have tools like GitHub and Source Tree – we needed a similar version control system. Enter Folio, Plant and Abstract.

    Folio

    Folio is a Mac only app that allows you to join existing Git repositories. You can clone existing projects from anywhere, including Gitlab, Github, Bitbucket or your own Git server. Folio will automatically stay in sync. Unlike the other apps it has no Sketch plugin and is available for free in trial mode through a Free Viewer. It works with most files (Photoshop, Illustrator, Sketch, Antetype & SVG out of the box) and you can add support for most files that have Quicklook previews.

    Folio makes it quite easy to add a Git repository and allows you to browse all files assets and work with them. When you update your Sketch file, Folio will automatically update (commit and push) it. It also keeps all versions of a file, so you can easily review them. However, it’s a bit messy since there is no folder organisation – just one library. Also, In Folio every commit you make is on a single file. Select a file on the left, show its version history on the right. Other than your written description, the only way to see which components on which artboards were changed is to compare them visually.

    Plant

    Plant is also a Mac only app with a Sketch plugin. It includes Cloud sync, version history, conflict resolution but seemingly you cannot use Plant with other version controlled softwares than the proprietary one (currently you get 1 GB). It’s based on pages (and has filtering options) – it recognises which artboards you made changes to and suggest them as changes ready to be sent. It has the ability to show only the modified artboards as well as comments to each version in a pretty neat preview mode of the artboard. It also syncs pretty fast. We only wish it was more clear as to what is the actual master file (since that’s our main goal). Right now, each version you change is listed in the history on the right and branching and merging is not as clear as we would like them to be.

    Abstract

    We have to give it up to Abstract as they seem to be our favourites. It took us no time to find our way around the app as it’s very clear what your commits and branches are as well as what the master file is. We had two questions for the crew that generously invited us to try out the Private Alpha – when can we buy Abstract and can we use our own GitHub (since that is what the company uses as a versioning tool) to clone projects and stay in sync. At the moment you can’t use Abstract with other version controlled softwares (i.e. Github and Bitbucket) than the proprietary one. We’re still waiting for a reply on if that would change. Currently it is available only for Mac and Sketch but it was announced that it would also be available for Adobe.

    As they put it on their blog “Throw Abstract in the mix, and suddenly everyone on a team has access to a rich history of why certain things are the way they are, the decisions that led to the current state of the work, and a way to instantly start contributing in a meaningful way.” Can’t wait for the Public Beta!

     

    I also have to give thanks where thanks is due – to the backend developer who helped create this article. Thanks for all the help and collaboration!

    The post Version control systems in UI design appeared first on Inchoo.


    Bypassing Magento 2 layout update restrictions in Admin

    $
    0
    0

    Magento’s layout system has been vastly improved in Magento 2 to be more accessible, more powerful and to be more streamlined. But, for some reason, only a very small subset of layout functions is made available for use in Magento 2 Admin (category layout XML updates, to be more precise). In this article, we’re going to show a very simple way of bypassing this pesky restriction.

    Layout update restrictions in Magento 2 Admin

    In the interest of keeping the example simple and straightforward, let’s say we want to remove the page title element from a specific category. In Magento 2 admin, in the category layout update XML field we add the following line.

    <referenceBlock name="page.main.title" remove="true" />

    And when we click save, we get the following error:

    Please correct the XML data and try again. Element ‘referenceBlock’: This element is not expected. Expected is one of (referenceContainer, container, update, move ). Line: 1

    It’s important to note that this XML would be valid if added using XML files in a theme. It seems that different standards apply for the layout update field in Magento 2 Admin and for the theme layout files.

    So, only the following XML is allowed:

    • referenceContainer – we can reference existing containers
    • container – we can create new containers
    • update – we can include various layout handles
    • move – we can move existing blocks around

    This info will help us bypass the restriction.

    Bypassing the layout update restrictions

    We can simply bypass this restriction by wrapping our referenceBlock code in referenceContainer code. But we need to find the container which contains our page title element. By doing a simple search in IDE, we find the following code snippet.

    <referenceContainer name="columns.top">
    <!-- ... -->
        <block class="Magento\Theme\Block\Html\Title" name="page.main.title" template="html/title.phtml"/>
                <container name="page.messages" htmlTag="div" htmlClass="page messages">
                    <block class="Magento\Framework\View\Element\Template" name="ajax.message.placeholder" template="Magento_Theme::html/messages.phtml"/>
                    <block class="Magento\Framework\View\Element\Messages" name="messages" as="messages" template="Magento_Theme::messages.phtml"/>
                </container>
    <!-- ... -->
    </referenceContainer>

    We use the columns.top container and reference it in our Layout Update XML in Admin and wrap it around our initial code.

    <referenceContainer name="columns.top">
        <referenceBlock name="page.main.title" remove="true" />
    </referenceContainer>

    When we save this configuration, we do not get any errors on the category page in Admin and the page title block is removed on that specific category.

    Conclusion

    We hope to see these restrictions either removed from Magento 2 Admin XML layout update fields or better documentation that will explain the behavior in more detail. Magento 2 Admin would most certainly benefit of having unrestricted layout update features. Until then, we can hopefully use this bypass safely and hope that it’s upgrade-proof.

    The post Bypassing Magento 2 layout update restrictions in Admin appeared first on Inchoo.

    Klevu – search engine that will increase revenue, self-learn and improve UX

    $
    0
    0

    One of most important parts of every store is its Search functionality. Implementing a more advanced search solution can have positive and big impact on search function, which leads to significant increase in conversion rate. After all, visitors trust in search results. If your store’s search experience is good, users will use it more than browsing by clicking through categories.

    With artificial intelligence and self learning processes, search engines became even more powerful and provide us with features that were not possible before.

    Klevu is one of those search engines – it is fast (really fast), it provides high level of customisation and most important – it learns from your customers and self-improves which results in more accurate search results and increase in revenue.

    A few key takeaways from developer’s side:

    Responsive works well out of the box, styling is decent and code is something you can work with.

    It’s dynamic filters automatically create all relevant filters in the search results.

    Error tolerance, as an enhanced keyword search index, ensure that shoppers are always connected to the right products.

    Klevu works with both Magento 1 and Magento 2.

    (for full feature list, check this link)

    Installing Klevu

    Installation is pretty much straightforward and same as with other extensions so i won’t talk much about it. After Extension has been installed, you must configure it and create your account.

    Go to:

    System > Configuration > Klevu > Search configuration

    and start configuration wizard:

    There are some prerequisites to be met, so check them out in case of any issues.

    Klevu Dashboard

    When you finish with Klevu wizard, you will be provided with credentials to access Klevu Dashboard where you can configure and edit Klevu to suit your needs.

    There are many options over there so go through tabs and get yourself familiar with features.

    In terms of styling, you will have to edit 2 areas of Klevu search – Search results dropdown and Search results page.

    Under Customization section, you can change the look and feel of your Klevu search.

    In Search as You Type Layout tab, you can define the look of search dropdown:

    You can choose whether you will use Instant faceted search or Instant autocomplete layout first one displays filters on the left while second one is displaying suggestions and results under.

    You can choose between grid or list display, as well as how many products you wish to feature. Pretty much everything you need for start.

    Klevu Styling

    Under Customize CSS tab, you can edit CSS used for styling of search results dropdown.



    You can then save changes and preview the styling on your testing site. Sometimes, Klevu needs few minutes to update changes so be patient. 🙂

    If you click on Search results page tab, you will see screenshots and explanation on how to change styling of search results page.

    As you can see, it is not possible to change CSS in Klevu Dashboard (like we did for search suggestions), instead we are informed that we can use our own default Magento search results page (in which case we will have our theme styling and no additional CSS modifications will be needed) or we can use Klevu search results page. In that case, styling is done like with any other extension – editing files locally.

    Same story as always – copy files from base to your package/theme and start with your magic.

    Klevu data

    By default, Klevu uses the following attributes in search: name, sku, description, short_description, price and rating.

    You can add other custom attributes as well:

    As you can see, you can tell Klevu which attribute values will be used in search. Klevu works great with receiving data and sending results back to user but if you want to display some custom product data on search results page, you may be in problem.

    Read next…

    Klevu Search results page

    You can use two types for search results page – Klevu search result page or Magento search results page. Although decision seems pretty much straightforward, there are few differences that you must be aware before making that decision.

    If you use:

    • Magento search results page
      – no need for additional styling (if search results page is already styled)
      – no need for additional structural changes
      – attributes – multiselect doesn’t work
      – ajaxed search results don’t work
    • Klevu search results page
      – instant, fast ajaxed results
      – multiselect Attributes
      – requires styling
      – custom product-related information not shown

    Using Klevu search results page may cause an issues with custom data attached on your products. Although you can send additional data to Klevu and Klevu will provide results based on that data, it will not send that data back which means these informations will be missing on frontend. Reason for that is simple – Klevu is not aware of custom data shown on your category listing/search results page. You can define which custom attributes will be included in search but Klevu is not sending that data back, it is only using it to output search results.
    That is a problem since that custom data can be really important for users.

    Hopefully, there is a solution – Klevu support team. Of course, you can’t modify core functionalities by yourself so you will have to contact these guys. They will then do necessarily changes on their end or you will receive additional code to implement on your site.

    I must say that we did this kind of work with klevu support and they did great job. They always react fast and get the job done.

    Big question now is – Should we use default Magento search results page template or we should go with Klevu?
    Basically, it all depends on project scope.

    For heavier projects – it is better to use Magento search results page as in that case you will have your own structure and control over what is shown. On the other side, you will have to do Ajax functionality by yourself, as well as multiselect of attributes in layered navigation.
    Using Klevu search results page means you will have to work with Klevu team and if there is a heavy customisation needed, whole process may take a while.

    For clean, straightforward project with less custom information, go with Klevu search results page as search speed is just amazing and like i said, results are ajaxed and multiselect works. Results page styling will not follow Theme design so that will be extra work from your side.

    Conclusion

    Klevu is definitely something you should try/suggest to your clients. For us, it has proven to be one of best search engines we have ever tried.

    Klevu is running on several of our projects and clients are satisfied with it.
    And the best part – it will only get better since it learns from users and adjusts search results accordingly.

    Klevu also offers 14 days of free trial which is enough time for first impressions and looking “under the hood”.

    If you wish to know more, go on their site and check out integration guides or other documentation – everything is well written and explained.

    We’ve implemented Klevu for our client BAUHAUS Croatia. This home and garden specialists have seen great results after achieving new development milestone, which also included implementation of Klevu search. 74% higher eCommerce Conversion rate, 105% increase in total transactions and 143% jump in Revenue!

    If these numbers sound great, let’s see what we can achieve for your store!

    The post Klevu – search engine that will increase revenue, self-learn and improve UX appeared first on Inchoo.

    Override Magento 2 Layout: Product page example

    $
    0
    0

    Layouts play a major roll in Magento. This roll is well known from Magento 1x layout approach. With Magento 2 couple of things are changed and improved. Really, we finally have on our disposal useful layout manipulation options.

    Layout basics

    If you are here just for example and you already familiar with Magento 2 Layout, skip it this section.

    Layout is a page structure, represented by elements hierarchy, which can be: blocks and containers.
    Technically, layout is defined in the .xml files. Files which contain element declarations and different manipulation instructions.

    Module and theme layout files

    Magento 2 layouts are provided by different application components. We can split them in two major groups Base layout and Theme layout. Let us mention main difference between them.

    Base layouts

    This Layout files are provided by modules. We can find page configuration and generic layout files on this path:
    module_dir/view/frontend/layout

    And page layout files on path:
    module_dir/view/frontend/page_layout

    The main file of Base layout, if you wish to check it, is located on path:
    Magento_Theme_module_dir/view/frontend/layout/default.xml

    Theme layouts

    Theme layouts are logical files provided by Magento themes. Here we can find page configuration and generic layout files. Check them on path:
    theme_dir/Namespace_Module/layout

    Also, the page layout files are on path:
    theme_dir/Namespace_Module/page_layout

    Override a layout

    Above mentioned basic are necessary in order to understand how to properly override Magento 2 layout. The layout needs to be properly overridden when we perform some kind of customization, for example when we:

    • Modify method arguments
    • Remove blocks and/or containers
    • Set up XML attributes of blocks and containers
    • Remove block arguments
    • Modify and suppressing handles inclusion
    • Remove all handle instructions by declaring an overriding layout file with an empty handle

    Override base layouts

    To override base layout file (layout provided by the module) we need to put a layout file with the same name in the following location:
    /Namespace_Module/layout/override/base/layout.xml

    These file override the following layout:
    /view/frontend/layout/layout.xml

    Override theme layouts

    To overriding theme layout (override a parent theme layout) put a layout file with the same name in the following location:
    theme_dir/Namespace_Module/layout/override/theme/Vendor/theme/layout.xml

    These files override the following layouts:
    parent_theme_dir/Namespace_Module/layout/layout.xml
    To override page layout files, use ‘page_layout’ directory name instead of ‘layout’.

    Product page override

    Now when we are familiar with layout overriding rules, let’s make a real life example. Let’s override product page layout.

    Overriding product page for specific products

    For example, our client has two or more types of products and on top of that, client also has one special product which is totally different. So client also wishes to present that product in different product page. Basically, we need at least three different product page layouts.

    Luckily Magento 2 provides this kind of flexibility by using native addPageLayoutHandles method. Mentioned method provides possibilities of overriding layout using:

    • Product ID
    • Product SKU
    • Product TYPE

    On top of that, method will also support your custom product type. For instance if we create a product type with the name “inchoo”, you can then create a file called catalog_product_view_type_inchoo.xml to handle specific layouts for this type of products.

    So, let start first with overriding layout for just one specific product.

    In order to do that we need to follow this steps:

    1. Create new layout file inside our theme scope, the name of layout file will be: catalog_product_view_id_number and we’ll place it in:
      theme_dir/Namespace_Module/layout/catalog_product_view_id_number.xml

      Similar like this:

    2. Override what we need inside of layout file (remove blocks, add new blocks etc.).
    3. Clear Magento’s cache and reload page.

    We are preferring to use product ID. But if product ID isn’t good option for you, you can use SKU number for targeting product, in that case your layout file need have name in this form:
    catalog_product_view_sku_productsku.xml

    Now let’s create unique product page for specific product type. As you probably know Magento by default has few different product types (bundle, configurable, downloadable etc.). So for example, if we wish to have different layout for downloadable products, we need to use corresponding product type name to target that layout.

    In order to override downloadable products, follow this steps:

    1. Create new layout catalog_product_view_type_downloadable.xml file inside Magento theme.

      theme_dir/Namespace_Module/layout/catalog_product_view_type_downloadable.xml

    2. Override what we need inside of layout file (remove blocks, add new blocks etc.).
    3. Clear Magento’s cache and reload page.

    Same naming principle can be followed for other type of products, like this:

    catalog_product_view_type_simple.xml
    catalog_product_view_type_configurable.xml
    catalog_product_view_type_grouped.xml
    catalog_product_view_type_bundle.xml
    catalog_product_view_type_virtual.xml
    catalog_product_view_type_downloadable.xml

    Important note: Current Magento 2 version has bug with configurable product type. Unfortunately it isn’t possible to override configurable products just by creating new catalog_product_view_type_configurable.xml layout file. We hope that the next version of Magento will solve this bug. Stay tuned here.

    The example is short but I hope that this article can help you with overriding layouts.

    If you have issues on your web store and you need frontend assistance, contanct us for usability audit.

    Regards.

    The post Override Magento 2 Layout: Product page example appeared first on Inchoo.

    CSS Media queries in Magento 2

    $
    0
    0

    It’s highly unlikely that there are some frontend developers involved in a responsive website development who are not familiar with the concept of CSS Media queries. In this post, I’ll be looking at Magento 2 way of implementing this functionality in its default theme development workflow.

    How do Magento 2 media queries work?

    In the heart of Magento 2 media queries mechanism, defined in Magento UI library, is a .media-width() mixin.

    .media-width(<@extremum>, <@break>);

    @extremum: max|min – sets whether to use min-width or max-width in media query condition

    @break: value – sets the value of breakpoint to compare with in media query condition

    We can see the practical use of it in the following example:

    // In .less file
    .media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__s) {
        your styles
    }
     
    // After compilation, in .css file
    @media only screen and (max-width: 640px) {
        your styles
    }

    This mixin, which is used for grouping style rules in certain media queries, can be used whenever you need it, in any .less file in your theme. In the end, it will be invoked only once – in lib/web/css/source/lib/_responsive.less file.

    If you check the end product of .less files compilation, styles.css file, you’ll see there each media query with all its rules only once, so there are no a multiple calls for the same query.

    Default settings

    By default, media queries are outputting styles in two different files:

    1. styles-m.less – which generates basic and mobile-specific styles

    2. styles-l.less – generates desktop-specific styles (for 768px width screen and higher)

    This means that, when the website is opened on a mobile device, it will load only styles compiled from the styles-m.less file. Extra styles from the second file (styles-l.less) are compiling and loading only if the screen width is 768px or higher.

    For this style groups separation, special variables @media-target and @media-common are used:

    // For targetting device for styles output
    @media-target: all|desktop|mobile
     
    // In practice
    & when (@media-target = 'mobile'), (@media-target = 'all') {
        @media only screen and (max-width: (@screen__xs - 1)) {
            .media-width('max', @screen__xs);
        }
    }
     
    // For deciding whether to output common styles.
    @media-common: true|false
     
    // Adding common styles
    & when (@media-common = true) {
        your styles
    }

    Out of the box, Magento UI library has a set of predefined variables for breakpoints. We can use them in any case we need them and they can be changed or extended them with a new ones:

    • 320px
    • 480px
    • 640px
    • 768px (breakpoint for switching between mobile and desktop views)
    • 1024px
    • 1440px

    Custom breakpoints

    Quite often, we’ll find ourselves in a situation where we’ll need to add an additional breakpoint (apart from default ones) in which we can apply a different styling. There are three things which need to be done in order to add a custom breakpoint in our theme.

    1. Define a variable for the new breakpoint.

    First, we’ll have to create a /web/css/source/variables.less file. If we would use a standalone theme, this file would be new and empty. But, since we’re following the best practice (inheriting from Magento Blank theme), we’ll have to copy this file from our parent theme.

    Here we can define our custom breakpoint:

    @custom__breakpoint: 1280px;

    2. Implement our new breakpoint

    To do so, we’ll override _responsive.less file from Magento UI library by copying it to our theme in /web/css/source/lib/. Now, in our theme’s _responsive.less file we have to edit .media-width() mixin by adding the appropriate rule for the new custom breakpoint.

    & when (@media-target = 'desktop'), (@media-target = 'all') {
        @media all and (min-width: @custom__breakpoint) {
            .media-width('min', @custom__breakpoint);
        }
    }

    3. Implement the screen changes for the new breakpoint.

    If everything is set properly, we should be able call our new .media-width() mixin whenever we need it in our theme .less files.

    .media-width(@extremum, @break) when (@extremum = 'min') and (@break = @custom__breakpoint) {
        // Stlying applied at @custom__breakpoint breakpoint
    }

    A small helper

    In the end, to wrap things up, I’ll give you some quick jump-start piece of code; media queries which you can quickly apply, depending on which screen size you’d like to target.

    // Common
    // (for styles used in both mobile and desktop views)
    & when (@media-common = true) {}
     
    // Mobile
    // (for all mobile styles.)
    .media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) {}
     
    // Tablet
    .media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) {
     
    // Desktop
    .media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__l) {}

    Thank you for reading!

    The post CSS Media queries in Magento 2 appeared first on Inchoo.

    How to run a quick security check of your Magento store?

    $
    0
    0

    Security of any software system, let alone an eCommerce one, is becoming one of the hottest topics out there. How can you as a store owner do a free and quick security check of your Magento website, even without immediate development assistance? Read on and make sure to stay secure!

    Magento has a vibrant ecosystem and a huge community of people mostly willing to help. They often provide quality insights into their own findings by sharing the knowledge, in most cases for free. So, if you are a developer or a Magento store owner, you can find many useful tools out there to help you run your businesses. One such tool allows you to put your site through a quick security check and get some good pointers into how to solve some of the identified issues.

    What’s up with security of Magento shops?

    Any open source system, especially one of the leading eCommerce software platforms, is open to, among others, persons and organizations that don’t have only good intentions, to put it nicely. To put it accurately, there are a lot of hackers out there who are jumping at opportunities when certain security exploits are found. Some of them are even actively creating such opportunities for themselves.

    What is Magento doing?

    Magento is among rarely active open source eCommerce platforms when it comes to addressing any potential security exploits. They are releasing frequent security patches that should be applied by store owners or (in most cased) their development partners as soon as possible.

    What are the most important patches?

    Every security patch is important, however this all started back in 2015 with the infamous Shoplift Bug and SUPEE-5344 patch. This patch came out to help with the issue which saw thousands of websites vulnerable to potential store hijacking.

    So, if this patch is not applied to your store, make sure to act swiftly. Additionally, the patches you should be on the lookout are: SUPEE-7405 and SUPEE-8788. There are many more, but these specifically can cause quite a lot of issues for you and your customers.

    What can you do?

    If you are on the merchant side of the equation (store owner, manager, anyone handling daily operations really), you can do a quick security check of your store by simply running your website through MageReport scan. If you are a developer, well – you can do the same 🙂

    What is MageReport and what is it telling me?

    Here’s a snippet from their own website:

    This free service gives you a quick insight in the security status of your Magento shop(s) and how to fix possible vulnerabilities. MageReport.com is made by the Magento hosting specialists of Dutch provider Byte.

    Essentially, the service is looking for proof/indication that you have official Magento patches installed. It also does security check over some other more or less known threats that can be mitigated rather easily.

    IMPORTANT: If you are working with the latest versions of Magento 1 and/or Magento 2 – the latest stable releases being 1.9.3.3 (CE), 1.14.3.3 (EE), 2.1.7 (CE and EE) – chances are you are pretty safe, because in the process of upgrading your team have probably patched the site already. And there is also a chance that you can get false positives/negatives from this report in some cases. Still, it doesn’t hurt to check.

    What to do next?

    First off, if you’re seeing mostly red or orange in your scan results, you know you’re in a bad place. Depending on which patches your store are missing and your Magento version, the security of your store data may have been compromised.

    Not good

    Good

    No need to panic right away, though. You should get in touch with your development team and ask them about this. As MageReport says, their report isn’t 100% accurate because they don’t have direct access to your store’s code. So, if you have a development team you trust, you should be in good hands. Simply ask them what’s the status about some of the missing patches (whether those are indeed missing). Then – work out a plan together to improve the security of your overall installation.

    If you don’t have anyone actively working on or monitoring your store, you can get in touch with us directly to see how we can help.

    If you’re getting mostly green results – well, hats off to you and your development team. You’re keeping the installation mostly safe and up to date with the latest security patches. Keep up the good work and don’t let new patches slide by you 🙂

    Also, make sure to bookmark Magento Security center and keep your eye open on the incoming security news.

    Stay safe!

    The post How to run a quick security check of your Magento store? appeared first on Inchoo.

    Magento 2 with Slick Carousel

    $
    0
    0

    Slick is a great Carousel plugin good for any product list like Most Popular, Featured, Upsell, Cross sell products, or any other custom product list. I will guide you with step by step on how to add Slick Carousel and apply it on product list widget on Homepage.

    Lets get started!

    Installing Slick

    1. Slick. Version in time of writing this tutorial is 1.6.0.
    2. Unzip downloaded folder and copy slick.min.js to
      app/design/frontend/_YOUR_VENDOR_/_YOUR_THEME_/web/js/
    3. Create or edit requirejs-config.js file in
      app/design/frontend/_YOUR_VENDOR_/_YOUR_THEME_/web/
    4. Open requirejs-config.js and copy/paste this code:
      var config = {
          paths: {
              slick:        'js/slick'
          },
          shim: {
              slick: {
                  deps: ['jquery']
              }
          }
      };
    5. Create “vendor” folder in
      app/design/frontend/_YOUR_VENDOR_/_YOUR_THEME_/web/css/source/

      Copy slick.less and slick-theme.less into it.

    6. Clear cache and deploy files
    7. Open frontend, view source code and find in <head> tag below code
      <script type="text/javascript" src="http://_YOURDOMAIN_/pub/static/_VERSION_/_requirejs/frontend/_YOUR_VENDOR_/_YOUR_THEME_/en_US/requirejs-config.js"></script>

      In the bottom of the document you should see a code we added in step 4.

    Apply Slick to product list widget

    It below steps, we will initialise Slick to product list widget template file.

    1. Create “Magento_CatalogWidget” folder in the root of your theme and copy below folder in it.
      vendor/magento/module-catalog-widget/view/frontend/templates
    2. Open
      app/design/frontend/_YOUR_VENDOR_/YOUR_THEME/Magento_CatalogWidget/templates/product/widget/content/grid.phtml
    3. Initialise Slick Carousel before closing <?php endif; ?> with example code below
      <script>
          require([
              'jquery',
              'slick'
          ], function ($) {
              jQuery(document).ready(function () {
                  jQuery(".widget-product-grid").slick({
                      dots: true,
                      infinite: true,
                      speed: 300,
                      slidesToShow: 4,
                      slidesToScroll: 4,
                      responsive: [
                          {
                              breakpoint: 1280,
                              settings: {
                                  slidesToShow: 3,
                                  slidesToScroll: 3
                              }
                          },
                          {
                              breakpoint: 768,
                              settings: {
                                  slidesToShow: 2,
                                  slidesToScroll: 2
                              }
                          },
                          {
                              breakpoint: 600,
                              settings: {
                                  slidesToShow: 1,
                                  slidesToScroll: 1
                              }
                          }
                      ]
                  });
              });
          });
      </script>

    Create and insert product list widget in Homepage

    1. Open Magento Admin → Content → Pages → Home Page
    2. Content → Show/Hide Editor → Insert Widget → Widget Type(Catalog Products List) → Populate fields and choose Conditions (choose category you want or products for example)
    3. Save widget & save page
    4. Check your Homepage and you should have Slick carousel for product list widget
    5. Last step is to customise Slicks CSS for your needs!

    Thanks for reading!

    The post Magento 2 with Slick Carousel appeared first on Inchoo.

    Initial preparation for an eCommerce project

    $
    0
    0

    Working in web development company is really interesting, especially when you work in the company that deals with many different projects across many different industries. Every day is pretty challenging and you have to manage a lot of things to make it work. However, I can only imagine how you as our clients feel when you need to contact us about your new projects, as eCommerce can sometimes be a bit overwhelming even for seasoned players, not to mention the beginners…

    A lot of you are rather versed with Magento and eCommerce in general, but also a lot of you need our help with tech details. Be it that you are not really tech savvy or you simply had no need to have this sort knowledge previously. But at one point you all come to us and your journey begins.

    The way new clients approach us can be very different. Some of you come to us with very specific requests while some others come simply asking for help. Therefore, things can sometimes be rather straightforward while on the other hand sometimes we need to dig deeper to get to the bottom of it. Regardless of the road we need to take, we will take care of your needs no question about it.

    However, since timelines are almost always tight it is not a bad idea to prepare in advance if you are looking to speed things up a bit.

    I will do my best here to try and offer a sort of a simple guide for merchants, in hope that this will help some of our clients (and other companies clients) to prepare in advance when reaching out to an eCommerce agency.

    Good preparation is essential

    At the beginning of the project we usually ask a lot of questions. Sometimes, the details we ask for might be perceived as insignificant, which honestly can not be further away from the truth. We can certainly understand you being in a hurry and rushing your projects to start ASAP, but that is not necessarily the best approach. A bit of preparation can help things move along once the project starts, and can influence the end result in both qualitative and financial manner.

    The more experience you have with eCommerce, the more these things are evident to you. However, this kind of guidance is much needed by many as it can help put the project on the right track and not lose any pace at the beginning of it.

    Whether you are just starting your eCommerce channel, or planning an upgrade or replatform to Magento, there are certain things that would be good if prepared/researched in advance.

    COMPANY INTRO

    For starters we always like to hear a bit about your company and it’s whereabouts. It helps us put things into a perspective when trying to provide the approach that will make a difference. So we like to hear as many details about your company as possible, such as: industry, type of products/services, type of audience, geographic reach, future plans, main competitors etc. What we also value is hearing the details that might be perceived as subjective, such as: the feelings you wish your business/website to evoke etc. Again, all of this would help us see a bigger picture and finally provide you with a service fit to your business specifically.

    FEATURES

    Magento is a platform that has an abundance of features in its free edition (Magento Open Source). So if you are starting your eCommerce channel we would usually suggest you to start with native Magento features. However, if you are doing replatform we would need to know if there are any specific features that you have on your current site, that might not be a part of Magento feature set. Same goes for new Magento users that would like to implement specific features right from the beginning. Again, this category is almost without boundaries, as there are no limits to what we can code. If you want it – we can probably make it! Obviously budget needs to be taken into consideration.

    DATA MIGRATION

    If you are doing a replatform from some other system to Magento there is always a question of data migration. Same goes for upgrades from Magento 1 to Magento 2. Depending on the data you would like to migrate we would check the database structure and prepare a tool for migration. For upgrades to Magento 2, there is also an official migration tool that can be used in the process.

    EXTERNAL SYSTEMS

    It is crucial for us to know what kind of systems you would like to integrate with.
    Businesses are using various external systems and it is of paramount importance to integrate as much of them as possible. Covering all the points of your workflow is crucial in setting up automated processes wherever possible.

    So what are the most usual systems used, those that almost every client needs to connect/integrate with?

    Payment provider
    Which payment gateway would you like to use? Deciding which provider to choose is a business decision that would need to take into consideration: pricing (fees), model (classic/modern), etc. Magento offers a lot of options natively, and whatever is not covered we can custom code.

    Shipping provider
    Same as with payment gateways, Magento offers some options natively, and we cover whatever else is needed. Deciding on how to pick a shipping provider is not an easy task. It will depend on many details, such as: pricing, geography you are looking to cover, type of products you have in the store etc.

    ERP
    This is easily one of the most important, as well as most common systems we integrate our client’s sites with. Knowing which version you would like to use, as well as what data you would like to pull and how the workflows should be set up helps quite a bit. Having developer’s documentation for the ERP you are using would be perfect. That way we would be able to initially scan the documentation and check if there are any weak points we would need to focus right at the beginning. Getting that documentation from ERP vendor should not be a problem.

    BUDGET

    This is one very important factor from both your perspective as well as ours. Obviously it is very important because of the financial aspect but even more important is the approach we are going to use. The key here is to maximise the budget! Meaning we will do our best to provide the best possible model your money (budget) can buy. To be able to do that we would need to balance; needs vs wishes, custom code vs modules, and short term objectives vs long term objectives.

    What that means is that we will do our homework and analyse every detail, in an effort to suggest you a final solution that will perfectly fit your needs.

    TIMELINES

    Timelines are obviously very important, sometimes even critical. These often have a lot influence on the project but not always in a good way. Generally speaking, it is better to have some “breathing space” because then people in production tend to have better ideas and more time to rethink possible approaches. Also, with any project that is in development there is always a chance that something might not go as planned, especially with complex projects. And I am not talking solely about problems that may occur, I am talking about situations where clients change their mind about a design or a feature in the midst of the project. Any shift, be it the major one or something not as crucial, is expected to influence the timelines as well as the budget.

    After we lined up all these details it is hard not to mention the famous triangle, and it is even harder not to stress it once again. There are always those three that are in the loop: fast, cheap, good. But you only get to chose 2! Chose wisely! 😉

    Animated GIF  - Find & Share on GIPHY

    What’s next?

    After we gather all the above info we are in a position to more solidly talk about potential project ahead of us. Only then we are able to offer some ballpark numbers/timelines and talk about next steps.

    I hope this gives you a better idea of what is needed to approach an eCommerce agency, in a way that will ensure saving some valuable time and starting the project in a best possible manner.

    The post Initial preparation for an eCommerce project appeared first on Inchoo.


    Restrict website access – require log in

    $
    0
    0

    There might be times when you do not want your catalog to be publicly visible, especially if running a B2B shop. Reasons for this are numerous but, from our experience, prices are on top of the list. Unfortunately, when it comes to Magento (at least Open Source edition), this feature is not available out of the box. Lucky for us, implementation is pretty straightforward. Let’s dig in.

    The idea

    When a customer tries to access the store (catalog, CMS, checkout, etc.), redirect him to login form (if he is not already logged in).

    The plan

    When planning this feature, good thing to know about Magento is that every route is composed of 3 parts:

    1. Module name
    2. Controller name
    3. Action name

    We can use this piece of information on every request that comes to Magento, in order to see where the request is supposed to go.

    This is only half of the information we need. Additionally, we need to know whether customer is already logged in.

    Specific combination of conditions above should redirect the customer to login form.

    What would be the proper way to check for those conditions? Well, hooking into Magento’s dispatching process (namely, predispatch), we can create an observer that will check what route is requested, as well as whether customer is already logged in.

    1. Create a 3rd party module (e.g. Inchoo_WebsiteRestriction)
    2. Register an observer
    3. Implement logic
    The execution
    1. Done
    2. create file in Inchoo/WebsiteRestriction/etc/frontend/events.xml
      <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
          <event name="controller_action_predispatch">
              <observer name="restrict_website" instance="Inchoo\WebsiteRestriction\Observer\RestrictWebsite" />
          </event>
      </config>
    3. Create observer file at Inchoo/WebsiteRestriction/Observer/RestrictWebsite.php
      <?php
       
      namespace Inchoo\WebsiteRestriction\Observer;
       
      use Magento\Customer\Model\Context;
      use Magento\Framework\Event\Observer;
      use Magento\Framework\Event\ObserverInterface;
      use Magento\Store\Model\StoreManagerInterface;
       
      class RestrictWebsite implements ObserverInterface
      {
       
          /**
           * RestrictWebsite constructor.
           */
          public function __construct(
              \Magento\Framework\Event\ManagerInterface $eventManager,
              \Magento\Framework\App\Response\Http $response,
              \Magento\Framework\UrlFactory $urlFactory,
              \Magento\Framework\App\Http\Context $context,
              \Magento\Framework\App\ActionFlag $actionFlag
          )
          {
              $this->_response = $response;
              $this->_urlFactory = $urlFactory;
              $this->_context = $context;
              $this->_actionFlag = $actionFlag;
          }
       
          /**
           * @param Observer $observer
           * @return void
           */
          public function execute(Observer $observer)
          {
              $allowedRoutes = [
                  'customer_account_login',
                  'customer_account_loginpost',
                  'customer_account_create',
                  'customer_account_createpost',
                  'customer_account_logoutsuccess',
                  'customer_account_confirm',
                  'customer_account_confirmation',
                  'customer_account_forgotpassword',
                  'customer_account_forgotpasswordpost',
                  'customer_account_createpassword',
                  'customer_account_resetpasswordpost',
                  'customer_section_load'
              ];
       
              $request = $observer->getEvent()->getRequest();
              $isCustomerLoggedIn = $this->_context->getValue(Context::CONTEXT_AUTH);
              $actionFullName = strtolower($request->getFullActionName());
       
              if (!$isCustomerLoggedIn && !in_array($actionFullName, $allowedRoutes)) {
                  $this->_response->setRedirect($this->_urlFactory->create()->getUrl('customer/account/login'));
              }
       
          }
      }

    Following the execute method, we have a list of routes that are white-listed always (e.g. login, logout, create account, reset password, etc.). Reason for this is that customer should be able to access those routes regardless of him being logged in or not (i.e. there is no need to redirect customer to login form, if he is already trying to access that page).

    In addition to inspecting the correct route, we need to check whether customer is already logged in. There are at least 2 ways to check this, and one of them is implemented above. The other one would be to use Magento’s customer session object (\Magento\Customer\Model\Session) and its isLoggedIn() method. However, according to this stackexchange question, there are some issues with it, so we’re not using it this time.

    The end

    To conclude, if a customer is not logged in, he is redirected to login form where he can login/register. Using this, we restrict access to the store’s catalog and CMS pages.

    The post Restrict website access – require log in appeared first on Inchoo.

    Adding gallery tab to a CMS page

    $
    0
    0

    In this article I will demonstrate how you can create new tab into admin cms page so you can create image gallery similar as upload images for product.

    Step 1

    Register your module :

    <config>
    	<modules>
    		<Inchoo_Gallery>
    			<active>1</active>
    			<codePool>local</codePool>
    		</Inchoo_Gallery>
    	</modules>
    </config>

    Step 2

    Module configuration :

    <config>
    	<modules>
    		<Inchoo_Gallery>
    			<version>1.0.0</version>
    		</Inchoo_Gallery>
    	</modules>
    </config>

    Step 3

    We need to store images into database so we need to create a model, collection, resource and setup script.Our model will implement JsonSerializable so we need to implement jsonSerialize method which will return required json data about the model.

    Config:

    <!--....-->
    <global>
     <models>
      <inchoo_gallery>
       <class>Inchoo_Gallery_Model</class>
        <resourceModel>inchoo_gallery_resource</resourceModel>
       </inchoo_gallery>
       <inchoo_gallery_resource>
        <class>Inchoo_Gallery_Model_Resource</class>
        <entities>
         <gallery>
          <table>inchoo_cms_gallery</table>
         </gallery>
        </entities>
        </inchoo_gallery_resource>
         </models>
         <resources>
          <inchoo_gallery_setup>
           <setup>
            <module>Inchoo_Gallery</module>
           </setup>
          </inchoo_gallery_setup>
         </resources>
    </global>
    <!--....-->

    Model:

    class Inchoo_Gallery_Model_Gallery extends
    Mage_Core_Model_Abstract implements JsonSerializable {
     public function _construct() {
      $this->_init( 'inchoo_gallery/gallery' );
    	}
     
     public function jsonSerialize() {
      return [
        'id'               => $this->getId(),
        'file'             => $this->getFile(),
        'label'            => $this->getLabel(),
        'position'         => $this->getPosition(),
        'disabled'         => $this->getIsDisabled(),
        'label_default'    => $this->getLabel(),
        'position_default' => $this->getPosition(),
        'disabled_default' => $this->getIsDisabled(),
        'url'              => Mage::getBaseUrl( Mage_Core_Model_Store::URL_TYPE_WEB ) . 'media/gallery/' . $this->getFile(),
        'removed'          => 0
    		];
    	}
    }

    Resource:

    class Inchoo_Gallery_Model_Resource_Gallery
    extends Mage_Core_Model_Resource_Db_Abstract
    {
    	public function _construct() {
    		$this->_init('inchoo_gallery/gallery','id');
    	}
    }

    Collection:

    <?php
    class Inchoo_Gallery_Model_Resource_Gallery_Collection
    extends Mage_Core_Model_Resource_Db_Collection_Abstract
    {
    	public function _construct() {
    		$this->_init('inchoo_gallery/gallery');
    	}
    }

    Setup script:

    <?php
    $installer = $this;
    $installer->startSetup();
     
    $table = $installer->getConnection()
                       ->newTable( $installer->getTable( 'inchoo_gallery/gallery' ) )
                       ->addColumn( 'id', Varien_Db_Ddl_Table::TYPE_INTEGER, null,
    	                   array(
    		                   'identity' => true,
    		                   'unsigned' => true,
    		                   'nullable' => false,
    		                   'primary'  => true,
    	                   ), 'Value id' )
                       ->addColumn( 'cms_page_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null,
    	                   array(
    		                   'nullable' => false,
    	                   ), 'Cms Page id' )
                       ->addColumn( 'position', Varien_Db_Ddl_Table::TYPE_INTEGER, null,
    	                   array(
    		                   'unsigned' => true,
    		                   'nullable' => true,
    	                   ), 'Position' )
                       ->addColumn( 'file', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255,
    	                   array(
    		                   'nullable' => false,
    	                   ), 'File Name' )
                       ->addColumn( 'label', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255,
    	                   array(
    		                   'nullable' => true,
    	                   ), 'Label' )
                       ->addColumn( 'is_disabled', Varien_Db_Ddl_Table::TYPE_BOOLEAN, null,
    	                   array(
    		                   'nullable' => true,
    	                   ), 'Is Disabled' );
     
    $installer->getConnection()->createTable( $table );
     
    $installer->getConnection()->addForeignKey(
    	$installer->getFkName(
    		'inchoo_gallery/gallery',
    		'cms_page_id',
    		'cms/page',
    		'page_id'
    	),
    	$installer->getTable( 'inchoo_gallery/gallery' ),
    	'cms_page_id',
    	$installer->getTable( 'cms/page' ),
    	'page_id'
    );
    $installer->endSetup();

    Step 4

    Next we need to create our block and template file.

    Config:

    <!--....-->
     
    <global>
    <!--....-->
    		<blocks>
    			<inchoo_gallery>
    				<class>Inchoo_Gallery_Block</class>
    			</inchoo_gallery>
    		</blocks>
    </global>
    <!--....-->

    Create block:

    <?php
    class Inchoo_Gallery_Block_Adminhtml_Gallery
    	extends Mage_Adminhtml_Block_Widget
    	implements Mage_Adminhtml_Block_Widget_Tab_Interface {
    	protected $_uploaderType = 'uploader/multiple';
     
    	public function __construct() {
    		parent::__construct();
    		$this->setShowGlobalIcon( true );
    		Mage_Adminhtml_Block_Template::__construct();
    		$this->setTemplate( 'inchoo/gallery.phtml' );
    	}
     
    	protected function _prepareLayout() {
    		$this->setChild( 'uploader',
    			$this->getLayout()->createBlock( $this->_uploaderType )
    		);
     
    		$this->getUploader()->getUploaderConfig()
    		     ->setFileParameterName( 'image' )
    		     ->setTarget( Mage::getModel( 'adminhtml/url' )->addSessionParam()->getUrl( '*/cms_page/upload' ) );
     
    		$browseConfig = $this->getUploader()->getButtonConfig();
    		$browseConfig
    			->setAttributes( array(
    				'accept' => $browseConfig->getMimeTypesByExtensions( 'gif, png, jpeg, jpg' )
    			) );
     
    		return Mage_Adminhtml_Block_Template::_prepareLayout();
    	}
     
    	public function getUploader() {
    		return $this->getChild( 'uploader' );
    	}
     
    	public function getUploaderHtml() {
    		return $this->getChildHtml( 'uploader' );
    	}
     
    	public function getHtmlId() {
    		return 'media_gallery_content';
    	}
     
    	public function getGallery() {
    		return Mage::getModel( 'inchoo_gallery/gallery' )->getCollection()
    		           ->addFieldToFilter( 'cms_page_id', array( 'eq' => $this->getRequest()->getParam( 'page_id' ) ) );
     
    	}
     
    	public function getImageTypes() {
    		return array( 'image' => [ 'label' => 'Base Image', 'field' => 'post[image]' ] );
    	}
     
    	public function getImageTypesJson() {
    		return Mage::helper( 'core' )->jsonEncode( $this->getImageTypes() );
    	}
     
    	public function getJsObjectName() {
    		return 'media_gallery_contentJsObject';
    	}
     
    	public function getImagesJson() {
     
    		$jsonFiles = '';
    		$gallery   = $this->getGallery();
    		foreach ( $gallery as $images ) {
    			$jsonFiles = $jsonFiles . ',' . json_encode( $images );
    		}
     
    		return '[' . trim( $jsonFiles, ',' ) . ']';
    	}
     
    	/**
    	 * Prepare label for tab
    	 *
    	 * @return string
    	 */
    	public function getTabLabel() {
    		return Mage::helper( 'cms' )->__( 'Gallery' );
    	}
     
    	/**
    	 * Prepare title for tab
    	 *
    	 * @return string
    	 */
    	public function getTabTitle() {
    		return Mage::helper( 'cms' )->__( 'Gallery' );
    	}
     
    	/**
    	 * Returns status flag about this tab can be showen or not
    	 *
    	 * @return true
    	 */
    	public function canShowTab() {
    		return true;
    	}
     
    	/**
    	 * Returns status flag about this tab hidden or not
    	 *
    	 * @return true
    	 */
    	public function isHidden() {
    		return false;
    	}
     
    	/**
    	 * Check permission for passed action
    	 *
    	 * @param string $action
    	 *
    	 * @return bool
    	 */
    	protected function _isAllowedAction( $action ) {
    		return true;
    	}
     
    }

    Create template file :

    Create template. This template is based on gallery.phtml which is used in Mage_Adminhtml_Block_Catalog_Product_Helper_Form_Gallery_Content block. I modified this template to fit for our purpose.

    <?php
    /* @var $this Inchoo_Gallery_Block_Adminhtml_Gallery */
    ?>
     
    <div class="entry-edit-head">
        <h4 class="icon-head head-edit-form fieldset-legend">Images</h4>
        <div class="form-buttons"></div>
    </div>
    <div class="fieldset fieldset-wide" id="<?php echo $this->getHtmlId() ?>">
        <div class="hor-scroll">
            <table class="form-list" style="width: 100%;" cellspacing="0">
                <tbody>
                <tr>
                    <td class="value" colspan="3" style="width: 100%;">
                        <div id="<?php echo $this->getHtmlId() ?>">
                            <ul class="messages">
                                <li class="notice-msg">
                                    <ul>
                                        <li>
    										<?php echo Mage::helper( 'catalog' )->__( 'Image type and information need to be specified for each store view.' ); ?>
                                        </li>
                                    </ul>
                                </li>
                            </ul>
                            <div class="grid">
                                <table cellspacing="0" class="data border" id="<?php echo $this->getHtmlId() ?>_grid"
                                       width="100%">
                                    <col width="1"/>
                                    <col/>
                                    <col width="70"/>
    								<?php foreach ( $this->getImageTypes() as $typeId => $type ): ?>
                                        <col/>
    								<?php endforeach; ?>
                                    <col width="70"/>
                                    <col width="70"/>
                                    <thead>
                                    <tr class="headings">
                                        <th><?php echo Mage::helper( 'catalog' )->__( 'Image' ) ?></th>
                                        <th><?php echo Mage::helper( 'catalog' )->__( 'Label' ) ?></th>
                                        <th><?php echo Mage::helper( 'catalog' )->__( 'Sort Order' ) ?></th>
    									<?php foreach ( $this->getImageTypes() as $typeId => $type ): ?>
                                            <th><?php echo $type['label'] ?></th>
    									<?php endforeach; ?>
                                        <th><?php echo Mage::helper( 'catalog' )->__( 'Exclude' ) ?></th>
                                        <th class="last"><?php echo Mage::helper( 'catalog' )->__( 'Remove' ) ?></th>
                                    </tr>
                                    </thead>
                                    <tbody id="<?php echo $this->getHtmlId() ?>_list">
                                    <tr id="<?php echo $this->getHtmlId() ?>_template" class="template no-display">
                                        <td class="cell-image">
                                            <div class="place-holder"
                                                 onmouseover="<?php echo $this->getJsObjectName(); ?>.loadImage('__file__')">
                                                <span><?php echo Mage::helper( 'catalog' )->__( 'Roll Over for preview' ) ?></span>
                                            </div>
                                            <img src="<?php echo $this->getSkinUrl( 'images/spacer.gif' ) ?>" width="100"
                                                 style="display:none;" alt=""/></td>
                                        <td class="cell-label"><input type="text" class="input-text"
                                                                      onkeyup="<?php echo $this->getJsObjectName(); ?>.updateImage('__file__')"
                                                                      onchange="<?php echo $this->getJsObjectName(); ?>.updateImage('__file__')"/>
                                        </td>
                                        <td class="cell-position"><input type="text" class="input-text validate-number"
                                                                         onkeyup="<?php echo $this->getJsObjectName(); ?>.updateImage('__file__')"
                                                                         onchange="<?php echo $this->getJsObjectName(); ?>.updateImage('__file__')"/>
                                        </td>
    									<?php foreach ( $this->getImageTypes() as $typeId => $type ): ?>
                                            <td class="cell-<?php echo $typeId ?> a-center"><input type="radio"
                                                                                                   name="<?php echo $type['field'] ?>"
                                                                                                   onclick="<?php echo $this->getJsObjectName(); ?>.setProductImages('__file__')"
                                                                                                   value="__file__"/></td>
    									<?php endforeach; ?>
                                        <td class="cell-disable a-center"><input type="checkbox"
                                                                                 onclick="<?php echo $this->getJsObjectName(); ?>.updateImage('__file__')"/>
                                        </td>
                                        <td class="cell-remove a-center last"><input type="checkbox"
                                                                                     onclick="<?php echo $this->getJsObjectName(); ?>.updateImage('__file__')"/>
                                        </td>
                                    </tr>
                                    <tr id="<?php echo $this->getHtmlId() ?>-image-0">
                                        <td class="cell-image"><?php echo Mage::helper( 'catalog' )->__( 'No image' ) ?></td>
                                        <td class="cell-label"><input type="hidden"/>&nbsp;</td>
                                        <td class="cell-position"><input type="hidden"/>&nbsp;</td>
    									<?php foreach ( $this->getImageTypes() as $typeId => $type ): ?>
                                            <td class="cell-<?php echo $typeId ?> a-center"><input type="radio"
                                                                                                   name="<?php echo $type['field'] ?>"
                                                                                                   onclick="<?php echo $this->getJsObjectName(); ?>.setProductImages('no_selection')"
                                                                                                   value="no_selection"/>
                                            </td>
    									<?php endforeach; ?>
                                        <td class="cell-disable"><input type="hidden"/>&nbsp;</td>
                                        <td class="cell-remove last"><input type="hidden"/>&nbsp;</td>
                                    </tr>
                                    </tbody>
                                    <tfoot>
                                    <tr>
                                        <td colspan="100" class="last" style="padding:8px">
    										<?php echo Mage::helper( 'catalog' )->__( 'Maximum width and height dimension for upload image is %s.', Mage::getStoreConfig( Mage_Catalog_Helper_Image::XML_NODE_PRODUCT_MAX_DIMENSION ) ); ?>
    										<?php echo $this->getUploaderHtml() ?>
                                        </td>
                                    </tr>
                                    </tfoot>
                                </table>
                            </div>
                        </div>
                        <input type="hidden" id="<?php echo $this->getHtmlId() ?>_save" name="post[media_gallery][images]"
                               value="<?php echo $this->escapeHtml( $this->getImagesJson() ) ?>"/>
                        <input type="hidden" id="<?php echo $this->getHtmlId() ?>_save_image"
                               name="post[media_gallery][values]" value="{}"/>
                        <script type="text/javascript">
                            //<![CDATA[
                            var <?php echo $this->getJsObjectName(); ?> =
                            new Product.Gallery('<?php echo $this->getHtmlId() ?>', <?php echo $this->getImageTypesJson() ?>);
                            //]]>
                        </script>
                    </td>
                </tr>
                </tbody>
            </table>
        </div>
    </div>

    Step 5

    To add our block as new gallery tab we need to create new layout file and use existing action handle and reference to block which contain all tabs blocks in cms page.

    <layout>
    	<adminhtml_cms_page_edit>
    		<reference name="cms_page_edit_tabs">
    			<block type="inchoo_gallery/adminhtml_gallery" name="cms_page_edit_tab_gallery"/>
    			<action method="addTab">
    				<name>gallery_section</name>
    				<block>cms_page_edit_tab_gallery</block>
    			</action>
    		</reference>
    	</adminhtml_cms_page_edit>
    </layout>

    Step 6

    Finally we need to add logic for saving our gallery into database. We need to override Mage_Adminhtml_Cms_PageController and create our logic for saving and upload the data.

    Config:

    <config>
    <!--....-->
    	<admin>
    		<routers>
    			<adminhtml>
    				<args>
    					<modules>
    						<inchoo_gallery before="Mage_Adminhtml">Inchoo_Gallery_Adminhtml_Rewrite</inchoo_gallery>
    					</modules>
    				</args>
    			</adminhtml>
     
    		</routers>
    	</admin>
    <!--....-->
    </config>

    Create controller:

    <?php
    require_once Mage::getModuleDir( 'controllers', 'Mage_Adminhtml' ) . DS . 'Cms' . DS . 'PageController.php';
     
    class Inchoo_Gallery_Adminhtml_Rewrite_Cms_PageController extends Mage_Adminhtml_Cms_PageController {
     
    	public function saveAction(){
    	//Add this code after $model->save
     
    				$request    = $this->getRequest()->getPost();
    				$formImages = json_decode( $request['post']['media_gallery']['images'] );
     
    				foreach ( $formImages as $galleryImage ) {
     
    					//if new image is uploaded
    					if ( ! isset( $galleryImage->id ) ) {
     
    						if ( $galleryImage->removed == 1 ) {
    							$filePath = Mage::getBaseDir( 'media' ) . DS . 'gallery' . DS . $galleryImage->file;
    							if ( file_exists( $filePath ) ) {
    								unlink( $filePath );
    							}
     
    						} else {
    							$galleryModel = Mage::getModel( 'inchoo_gallery/gallery' );
     
    							$galleryModel->setCmsPageId( $model->getId() );
    							$galleryModel->setFile( $galleryImage->file );
    							$galleryModel->setPosition( $galleryImage->position );
    							$galleryModel->setLabel( $galleryImage->label );
    							$galleryModel->setIsDisabled( $galleryImage->disabled );
     
    							$galleryModel->save();
    						}
     
    					}
     
    					if ( isset( $galleryImage->id ) ) {
    						if ( $galleryImage->removed == 1 ) {
    							$filePath = Mage::getBaseDir( 'media' ) . DS . 'gallery' . DS . $galleryImage->file;
     
    							$galleryModel = Mage::getModel( 'inchoo_gallery/gallery' );
    							$galleryModel->setId( $galleryImage->id );
    							$galleryModel->delete();
     
    							if ( file_exists( $filePath ) ) {
    								unlink( $filePath );
    							}
     
    						} else {
     
    							$isModified = false;
     
    							if ( $galleryImage->label_default != $galleryImage->label ) {
    								$isModified = true;
    							}
     
    							if ( $galleryImage->position_default != $galleryImage->position ) {
    								$isModified = true;
    							}
     
    							if ( $galleryImage->disabled_default != $galleryImage->disabled ) {
    								$isModified = true;
    							}
     
    							if ( $isModified ) {
    								$galleryModel = Mage::getModel( 'inchoo_gallery/gallery' );
    								$galleryModel->setId( $galleryImage->id );
    								$galleryModel->setPosition( $galleryImage->position );
    								$galleryModel->setIsDisabled( $galleryImage->disabled );
    								$galleryModel->setLabel( $galleryImage->label );
     
    								$galleryModel->save();
     
    							}
    						}
     
    					}
     
    				}
     
    	//other code
           }
    	public function uploadAction() {
    		try {
    			$uploader = new Varien_File_Uploader( 'image' );
    			$uploader->setAllowedExtensions( array( 'jpg', 'jpeg', 'gif', 'png' ) );
    			$uploader->setAllowRenameFiles( true );
    			$uploader->setFilesDispersion( false );
    			$path   = Mage::getBaseDir( 'media' ) . DS . 'gallery';
    			$result = $uploader->save( $path );
    			/**
    			 * Workaround for prototype 1.7 methods "isJSON", "evalJSON" on Windows OS
    			 */
    			$result['tmp_name'] = str_replace( DS, "/", $result['tmp_name'] );
    			$result['path']     = str_replace( DS, "/", $result['path'] );
     
    			$result['url']    = Mage::getBaseUrl( Mage_Core_Model_Store::URL_TYPE_WEB ) . 'media/gallery/' . $result['name'];
    			$result['cookie'] = array(
    				'name'     => session_name(),
    				'value'    => $this->_getSession()->getSessionId(),
    				'lifetime' => $this->_getSession()->getCookieLifetime(),
    				'path'     => $this->_getSession()->getCookiePath(),
    				'domain'   => $this->_getSession()->getCookieDomain()
    			);
     
    		} catch ( Exception $e ) {
    			$result = array(
    				'error'     => $e->getMessage(),
    				'errorcode' => $e->getCode()
    			);
    		}
    		$this->getResponse()->setBody( Mage::helper( 'core' )->jsonEncode( $result ) );
    	}
     
    }

    And that’s it! Happy coding!

    The post Adding gallery tab to a CMS page appeared first on Inchoo.

    Minify your CSS and JavaScript code!

    $
    0
    0

    If you are reading this article, you are probably somehow involved with web development. It is also most likely that you already know how search engines use page speed as one of the parameters for evaluating your site. We have couple of options on our disposal when it comes to increasing site speed. Today we will cover one of them.

    Probably the simplest method of increasing site speed is minifying CSS and JavaScript. Even though this is pretty simple and straight forward method, for some reason, a lot of Magento stores and generally websites, don’t use this benefit. We have seen different cases where developers completely ignored code minification. Also, we have seen partially minified code. Partially means that, for example, theme code is minified but there is a lot of inline code which isn’t and it is possible to relocate and minify it. We’ve also seen cases where main theme code is minified but code from some other source (extension etc.) isn’t.

    Before we go any further let us just ensure that we are on the same page about what “minified code” means. So, minified code is code without:

    • Whitespace characters
    • New line characters
    • Comments
    • Block delimiters.

    Let us to suggest few possible options how to minify your code for production.

    Minifying CSS and JavaScript code

    In order to minify code we first need to decide are we going to do it manually or automatically. We’ll suggest to do automatically in order to speed up the development process, but if for some reason you want to do it manually, we don’t mind.

    Do It Manually

    CSS Minifying

    If you are a developer who doesn’t use any kind of frontend “wizard” on your project (SASS, LESS, Gulp, Grunt etc.), the best option for you will be to use some kind of online or offline minifying tool. For example, one of the online minifying tools you can use is cssminifier.
    If you are working on a complex project, you’re probably using some kind of IDE software. In that case you can speed things up by using some kind of IDE plugin that you can install or enable. That will minify the code every time when you save the working stylesheet file.

    JavaScript Minifying

    Very similar to minifying CSS code. There is bunch of online tools and different kind of IDE extensions for minifying JavaScript code. For example, good online tools are jscompress or javascript-minifier.

    Do it automatically

    This approach highly depends on the project and what we can or can’t change on it. Sometimes, it’s not possible to change the whole development environment just like that, especially if we’re collaborating with other developers on the same project. It’s not easy to suggest the best possible solution for everyone, but we’ll share what are we using on our Magento projects. We will assume that our audience is familiar with SASS, Compass, Gulp etc. If you need more information, please check out this article from my colleague. You can learn a lot of new things there.

    SASS

    If you already using SASS on your project, this will be easy for you. Just add additional parameter on sass –watch command:

    sass --watch file.scss:file.css --style compressed

    But, if you are not familiar with SASS then keep reading, maybe you will find even something better for you.

    Compass

    If project has Compass included, we already have Compass config file in the project. Just search for config.rb file, open the file and you should see something that looks something like the following:

    # Require any additional compass plugins here.
     
    # Set this to the root of your project when deployed:
    http_path = "/"
    css_dir = "stylesheets"
    sass_dir = "sass"
    images_dir = "images"
    javascripts_dir = "javascripts"
     
    # You can select your preferred output style here (can be overridden via the command line):
    output_style = :compressed # :expanded or :nested or :compact or :compressed
     
    # To enable relative paths to assets via compass helper functions. Uncomment:
    # relative_assets = true
     
    # To disable debugging comments that display the original location of your selectors. Uncomment:
    line_comments = false

    The config.rb file, Line 11 will likely be commented with the # sign. Uncomment the output_style (remove #). Then restart Compass to start watching for changes in SCSS files. To trigger it enter the command:

    $ compass watch

    Gulp

    Gulp is a truly useful tool for better and faster frontend development. Here on Inchoo blog we have already mentioned few Gulp benefits. We often suggest using Gulp as one of the best tools for complex projects such as Magento frontend development itself.
    With Gulp we have wide range of tools at our disposal which can be used to solve different kinds of development problems. But, currently we are only interested in code magnification tasks.

    There is couple of ways to handle code magnification in Gulp, but we prefer these two Gulp tasks for that:

    Minify CSS and JS code in Magento 1.x

    If we take Magento 1.x for example, we need to handle with a lot of inline CSS and Javascript code inside template files. And in order to achieve the best possible results with code magnification, we need to take a look at your Magento template files and find all CSS and JavaScript code that we can transfer to our theme files. Keep in mind that in some cases we will not be in position to transfer all of them to appropriate theme files. Mostly in cases where JavaScript is depending on some php input/output.

    Also, we suggest that while we’re transferring JavaScript code to theme file, we double check what code we need on which page. It is possible that we don’t need everything loaded on all Magneto pages. For example, some chunk of code is required just on the product page or just on the home page. That gives us possibility of splitting code in two different files which we can call on different occasions by using Magneto XML engine.

    Let us create new gulp tasks which we will using for fast minifying code:

    var gulp = require('gulp');
    var sass = require('gulp-sass');
    var concat = require('gulp-concat');
    var rename = require('gulp-rename');
    var uglify = require('gulp-uglify');
     
    // Css paths
    var cssInput = '../sass/**/*.scss';
    var cssOutput = '../css';
     
    // script paths
    var jsSkinInput = '../js/**/*.js';
    var jsSkinOutput = '../js/';
     
    // Js min task
    gulp.task('skin-scripts', function() {
        return gulp.src(jsSkinInput)
            .pipe(uglify())
            .pipe(rename({
                suffix: '.min'
            }))
            .pipe(gulp.dest(jsSkinOutput));
    });
     
    // Sass task
    gulp.task('sass', function () {
        return gulp.src(cssInput)
            .pipe(sass({
                errLogToConsole: true,
                outputStyle: 'compressed'
            }))
            .pipe(gulp.dest(cssOutput))
    });
     
    // Sass file watcher
    gulp.task('sass-watch', function() {
        return gulp
            .watch([cssInput, cssOutput], ['sass','browser-reload'])
            .on('change', function(event) {
                console.log('Css File ' + event.path + ' was ' + event.type + ', running tasks...');
            })
    });
     
    // Js file watcher
    gulp.task('skin-scripts-watch', function() {
        return gulp
            .watch([jsSkinInput,jsSkinOutput], ['skin-scripts','browser-reload'])
            .on('change', function(event) {
                console.log('Js File ' + event.path + ' was ' + event.type + ', running tasks...');
            });
    });
     
    gulp.task('default', ['skin-scripts', 'skin-scripts-watch', 'sass', 'sass-watch']);

    As we can see, we are watching for changes on all JavaScript and SASS files in our theme structure. Gulp recognized it and after few seconds Gulp generated minified code at the end. The first two task (skin-scripts and sass) are responsible for CSS and JavaScript minifying, and they work very similar, only difference being that JS files are renamed after minifying process by adding suffix .min at the end of file name. We wish to keep original file for further work and adjustments.

    When you are ready, enter this command in your terminal in order to start Gulp tasks:

    gulp default

    Sounds simple right. But with Magento there’s always something additional 🙂

    The thing is that Magento uses JavaScript not just in themes scope, but on the whole system. Major part of JavaScript which is responsible for keeping the system running is located in Magento root (js folder).

    As we mentioned before, our goal is to have full minified JavaScript code. And we wish to do things properly. So we have two options:

    First one is to adjust our Gulp task so that our task minifies root JavaScript code also, but that isn’t the best possible option. Why? Because with that action we will additionally complicate further maintenance and Magento upgrades. So, in order to avoid that, we want to minify root JavaScript code through Magento backend adjustments. Just before Magento JavaScript code merge.

    The second option have been presented by my colleague Tomislav, check the article here.

    Keep reading our blog and let us know what you think in the comments below 🙂

    The post Minify your CSS and JavaScript code! appeared first on Inchoo.

    Road to the Code with Less Bugs?

    $
    0
    0

    I think that writing any non-trivial software without introducing bugs is close to impossible. World is complex, data used in programs is complex, handling all possible interactions of our code with said data is hard. There are probably bugs even in the short script that is accompanying this article. Sounds dark? Maybe it shouldn’t.

    Everybody who writes software is creating bugs, it is inevitable.

    There is huge amount of research about what causes bugs and there are various results and conclusions.

    My interpretation of the surface reading is that number of “bugs per line” is scaling in some way with total number of lines in program. For example, program with 1000 lines will have one bug, program with 2000 lines will have 3 bugs, and program with 50 000 lines will have 100 bugs. Personally, I feel like that is true as it kind of matches my experience. Programmers mostly agree about this.

    Another finding is that programs with more restriction about who (who = which code) and when (under which conditions) can modify some state also have less bugs. (Scientists say!) I agree, because it also matches my experience. In long functions with many variables or classes with lots of properties which are used later for logic there are more bugs. Especially if we are writing code under assumption that objects will always be in some finite amount of states that we accounted for or that variables will be configured in a certain way. (error calling Method() on null, index not found, Varien_Object returning null on getPropertty() instead of giving error)

    There is also something called cyclomatic complexity (amount of logical branches code can take).
    I hope there are least some of us who did not experience that feeling of dread upon reading logs and seeing an exception occurred in the middle of 500+ line method of class on a line which starts somewhere after character number 52…

    So, if we could somehow:

    • Make objects/variables which are always in valid state. Maybe by using static declarations, there will be another article about how PHP7 is improvement over PHP5 here.
    • Make sections of code smaller and easier to understand. Perhaps by reducing number of branches and loops.
    • Reduce overall amount of code. Perhaps by using higher level constructs, so we write less code.

    Honestly, I am not really sure if such ideas will really work. So far, my feeling is that it helps a lot. Maybe it will help more when I get more experience, maybe it is actually worse and I lack knowledge/skill to see it.

    But I can see there is lot of space for improvement. And I am very confident that we should at least examine some of the options and see how they work in practice.

    So, in this teaser post, let’s do something everybody enjoys!

    Parsing CSV files, and doing some analysis on that data!!!

    Input: CSV with UN employment data. Rows with years, work sectors and number of people working in each sector per given year (in 2002, there were 100 000 people working for the Government).

    Output: Statistic with percentages that show how many people worked in some sector for given year, and how those percentages changed over years.

    For the first post, we are going simple, only showing data for one country and for both genders.
    That gives us excuse to introduce generators. 🙂 Our goal is to show the table in HTML (ugly looking table but we won’t be concerned with styling, only with filling table with proper data).
    Performance is secondary goal, and it will not be tackled until we are satisfied with the code quality. Corectness and crash-resistance are primary goals!

    I will quickly show uses of some of the concepts and we will try to improve code as series go on.
    Not sure where we will end and if functional style is good fit for the problem overall but let’s see where it will take us. Along the way, we’ll also see if functional in PHP will be easier to understand than the same thing implemented in OOP way or old-school imperative approach.

    Code is on github

    https://github.com/grizwako/broadening_horizons

    We will start with closures.

    Closures are good for encapsulating data, without going full on object-oriented.
    http://php.net/manual/en/functions.anonymous.php
    http://culttt.com/2013/03/25/what-are-php-lambdas-and-closures/
    http://fabien.potencier.org/on-php-5-3-lambda-functions-and-closures.html

    Goes like this – you create some function which will have some data saved, and you can later call that function (with new parameters too).

    Example function is accessing values of CSV data rows by column names defined in CSV header line.

    function mappedRow(data-row, column-map)
     
    $entry = mappedRow(
        [4, 'bla@blo.com'],
        ['id' => 0, 'email' => '1]
    );
     
    echo $entry('email'); //bla@blo.com

    Implementation:

    /**
     * Creates dictionary-like closure which accesses array values by string keys.
     * So instead of $row[$map['key'] you can use $row('key')
     * @param $row
     * @param $columnMap
     * @return Closure
     */
    function mappedRow($row, $columnMap)
    {
        if(empty($columnMap)) {
            throw new LogicException('Column map must not be empty! Hint: reverse header row to use as column map');
        }
     
        //returns function which accepts one parameter and has saved values for $row and $columnMap
        return function($key = null) use ($row, $columnMap) {
            return $row[$columnMap[$key]];
        };
    }

     

    Second useful thing: generators!

    http://php.net/manual/en/language.generators.overview.php
    Generators are like iterators/array, but differ in the fact that they will give you data only when they are told to do so. We can use generator to read huge CSV file line by line, and we only take lines that we need.

    For now, you can think of keyword yield as “feed (return) value into caller, but save state of generator function for when it is called next”.

    Generator is somewhat like closure, it can also have some local variables saved for later usages.

    /**
     * Lazily read CSV file
     *
     * @param $filename
     * @param string $split
     * @param int $maxLineLength
     * @param array $columnMap
     * @return Generator
     * @throws Exception
     */
    function mappedCsvGenerator($filename, $split = ',', $maxLineLength = 0, $columnMap = [])
    {
        $fileHandle = fopen($filename,'r');
        if(FALSE === $fileHandle) {
            throw new Exception('Could not open file: '.$filename);
        }
        while (($data = fgetcsv($fileHandle, $maxLineLength, $split)) !== FALSE) {
            if(empty($columnMap)) {
                //Moves array pointer to next row
                $columnMap = array_flip($data);
                continue;
            }
     
            yield mappedRow($data, $columnMap);
        }
    }

    Let’s create instance of a generator; we are still not asking it to read data from CSV.

    $rows = mappedCsvGenerator('un_employment_1980-2008.csv');

    Next, we will not need all rows from CSV, only a few. Let’s pretend that we do not have the database system in which we can import data. Filter rows as we are only interested in data for one country and for both genders.

    It would be wonderful if we could somehow delay reading of the CSV rows to the point when we actually need them.

    Luckily, Nikita Popov created a really nice library that helps with using lazy collections. (Also, he is one behind a lot of cool stuff that PHP got recently. Generators included.)
    About Nikita: https://nikic.github.io/aboutMe.html
    Lazy collections: https://github.com/nikic/iter

    $stats = iter\filter(
        function($row) {
            return $row('Country or Area') === 'Croatia'
    		&& $row('Sex') === 'Total men and women';
        },
        $rows
    );

    CSV file is still unread!! We did not actually use the values, we only specified that the values we will use will have to satisfy some condition. Instead of creating an array and then inserting rows from some foreach, we only said that $stats will give us rows that satisfy some condition when we ask for them.

    Grouping values with common higher order functions

    Next step, I think that we will need to group values by work sector and year if we want to display them in a table.

    Higher order just means function that accepts other function in a parameter, nothing actually fancy.
    Word group sounds like it should be a function, but there is not one such in Nikic’s library.
    Some searching will bring us to: https://github.com/lstrojny/functional-php

    It has group, it seems it can only do one level of grouping with one call. There is some advanced stuff in the library Lars made so maybe we can do some mumbo-jumbo when we revisit the code and refactor it to compose function that will group by two levels.

    First we group by work sector (subclassification). And then we do something that is actually elegant, but looks weird if you are not used to it. Map is kind of like foreach, but it is not supposed to modify collection it is iterating over. Think of it like: map = foreach item: call function “a” and return items function “a” returned.

    Function “a” will accept one item from the collection, do something and return one item. Map will return array of all items function “a” returned.

    $statsBySubclassification = group($stats, function($row){
        return $row('Subclassification');
    });
     
    $statsBySubclassificationAndYear = map($statsBySubclassification, function($subclass) {
        $indexed = reindex($subclass, function($row) {
            return (int)$row('Year');
        });
        return map($indexed, function($row) {
            return (float)$row('Value');
        });
    });

    So, with our grouped entries we will call function (which accepts all entries that fall under one Subclassifiction). That function will first create a new array which will have “Year” of entry as a key and after that go over all elements and get ‘Value’ only for each row.

    Something like:

    $statsBySubclassificationAndYear = [];
    foreach($statsBySubclassification as $subclassifiction => $items) {
        foreach($items as $item) {
        	//PHP has autovivification, so we dont have to worry about creating proper array keys
            $statsBySubclassificationAndYear[$subclassifiction][(int)$item('Year')] = (float)$item('Value');
        }
    }

    Honestly, in PHP foreach seems easier at first, even to me.
    In some made up functional language, we would have this sort of thing:

    statsBySubclassificationAndYear = map(statsBySubclassification, (subclass) =>
        indexed = reindex(subclass, (row) => (int)row('Year'))
        map(indexed, (row) => (float)row('Value'))
    )

     

    Displaying grouped data as a table:

    <?php
    $totalByYear = $statsBySubclassificationAndYear['Total.'];
    $years = array_keys($totalByYear);
    sort($years); // Why is this bad style?
     
    //now, lets build table that will show us suclassifications percentages over years
    // I feel that foreach is cleaner here, because we are mutating some object
    $table = new SimpleXMLElement('<table/>');
    $head = $table->addChild('tr');
    $head->addChild('th', 'Subclassification');
    foreach($years as $year) {
        $head->addChild('th', $year);
    }
     
    foreach($statsBySubclassificationAndYear as $subclass => $byYear) {
        $tableRow = $table->addChild('tr');
        $tableRow->addChild('td', $subclass);
        $percentage = 0;
        //we are relying on $years that was defined too far from where we are using it
        foreach($years as $year) {
            if(array_key_exists($year, $byYear)) {
                // can this part of code be improved by using functional style?
                // we are relying on $percentage from last iteration, stuff like that causes bugs
                $tempPerc = 100 *($byYear[$year] / $totalByYear[$year]);
                $delta = $tempPerc - $percentage;
                $percentage = $tempPerc;
                $procFormat = number_format($percentage, 2);
                $deltaFormat = number_format($delta, 2);
                $tableRow->addChild('td', $procFormat)->addChild('p', $deltaFormat);
            } else {
                $tableRow->addChild('td', ' - ');
            }
        }
    }
     
    ?>
     
    <style>
        table, th, td {
            border: 1px solid black;
        }
        p {
            color: blueviolet;
        }
    </style>
     
    Percentages by sector, blue is delta from previous year
    <?php echo $table->asXML(); ?>

    Conclusion of the first part:

    Article turned somewhat different than I expected. Having nicer lambda syntax would improve readability so much. Closures are great in limiting local scope, generators are really nice to use, in some other languages they require more syntax. Nikic does wonders for PHP.

    We have nicely formatted data, look at this design masterpiece!
    data screenshot

     Credits:

    UN data

    http://data.un.org/
    Total employment, by economic activity (Thousands)  [1980-2008]
    http://data.un.org/Data.aspx?q=employment&d=LABORSTA&f=tableCode%3a2B

    https://github.com/nikic/iter
    https://github.com/lstrojny/functional-php

    The post Road to the Code with Less Bugs? appeared first on Inchoo.

    Add Pagination to Custom Collection in Magento

    $
    0
    0

    If you are starting with Magento, just like I am, you might run into the issue of adding pagination to your table, list of products or anything else you want to list on frontend. Magento allows you to create your own pagination with adjustable built-in options. This tutorial will show you how to create pagination for a custom collection in Magento.

    This is how final custom pagination looks like:
    magento pagination

    I created Education module inside Inchoo package with Ticket model.

    magento pagination

    STEP 1

    First of all create custom action to access your content. Add a custom action inside your controller:

    public function paginationAction()
    {
        $this->loadLayout();
        $this->renderLayout();
    }

    STEP 2

    In your xml (app > design > frontend > rwd > default > layout > your_layout.xml) inside “layout” node add the node referencing your custom action. If you are using different or custom theme your path might be different than “rwd > default”.

    “education/pagination” – where “education” is the name of your module, “pagination” is name of your block inside pp/code/local/Inchoo/Education/Block folder, my block is named Pagination.php.

    <education_index_pagination>
      <reference name="content">
        <block name="collection_ticket" type="education/pagination" template="education/pagination.phtml"/>
      </reference>
    </education_index_pagination>

    Magento pagination

    STEP 3

    Create class that will extend Magento’s default product list toolbar so you can alter default behavior. Inside app/code/local/Inchoo/Education/Block create Toolbar.php

    class Inchoo_Education_Block_Toolbar extends Mage_Catalog_Block_Product_List_Toolbar
    {
        public function getPagerHtml()
        {
            $pagerBlock = $this->getLayout()->createBlock('page/html_pager');
            if ($pagerBlock instanceof Varien_Object)
            {
                /* here you can customize your toolbar like h*/
                $pagerBlock->setAvailableLimit($this->getAvailableLimit());
                $pagerBlock->setUseContainer(false)
                    ->setShowPerPage(false)
                    ->setShowAmounts(false)
                    ->setLimitVarName($this->getLimitVarName())
                    ->setPageVarName($this->getPageVarName())
                    ->setLimit($this->getLimit())
                    ->setCollection($this->getCollection());
                return $pagerBlock->toHtml();
            }
            return '';
        }
    }

    STEP 4

    Create your block inside your module’s block, in this case app/code/local/Inchoo/Education/Block. I named this block Pagination, you can call it however you want to, but then you have to be careful about calling it in your xml.

    class Inchoo_Education_Block_Pagination extends Mage_Core_Block_Template
    {
        public function __construct()
        {
            parent::__construct();
            //set your own collection (get data from database so you can list it)
            $collection = Mage::getModel('education/ticket')->getCollection();
            $this->setCollection($collection);
        }
        protected function _prepareLayout()
        {
            parent::_prepareLayout();
            //this is toolbar we created in the previous step
            $toolbar = $this->getLayout()->createBlock('education/toolbar');
            //get collection of your model
            $collection = Mage::getModel('education/ticket')->getCollection();
            //this is where you set what options would you like to  have for sorting your grid. Key is your column in your database, value is just value that will be shown in template
            $toolbar->setAvailableOrders(array('created_at'=> 'Created Time','id'=>'ID'));
            $toolbar->setDefaultOrder('id');
            $toolbar->setDefaultDirection("asc");
            $toolbar->setCollection($collection);
            $this->setChild('toolbar', $toolbar);
            $this->getCollection()->load();
            return $this;
        }
     
        //this is what you call in your .phtml file to render toolbar
        public function getToolbarHtml()
        {
            return $this->getChildHtml('toolbar');
        }
    }

    STEP 5

    Inside app > design > frontend > rwd > default > template (or change rwd > default to your theme path) create pagination.phtml (as you stated in step 2):

    <?php $collection = $this->getCollection();  ?>
        <div class="page-title">
            <h1><?php echo $this->__('My Collection') ?></h1>
        </div>
    <?php echo $this->getToolbarHtml(); ?>
        <table class="data-table" id="my-custom-table">
            <thead>
            <tr>
                <th><?php echo $this->__('Title') ?></th>
                <th><span class="nobr"><?php echo $this->__('Created at') ?></span></th>
            </tr>
            </thead>
            <tbody>
            <?php $_odd = ''; ?>
            <?php foreach ($collection as $item): ?>
                <tr>
                    <td><span class="nobr"><?php echo $item->getSubject(); ?></span></td>
                    <td><?php echo $this->formatDate($item->getCreatedAt()) ?></td>
                </tr>
            <?php endforeach; ?>
            </tbody>
        </table>
    <?php echo $this->getToolbarHtml(); ?>

    *Custom pagination

    If you want to create your own custom pagination without using Magento’s Toolbar, after you finished step 1 and step 2 inside app/code/local/Inchoo/Education/Block create Pagination.php.

    class Inchoo_Education_Block_Pagination extends Mage_Core_Block_Template
    {
        public function __construct()
        {
            parent::__construct();
            $collection = Mage::getModel('education/ticket')->getCollection();
            $this->setCollection($collection);
        }
        protected function _prepareLayout()
        {
            parent::_prepareLayout();
            $pager = $this->getLayout()->createBlock('page/html_pager', 'custom.pager');
            $pager->setAvailableLimit(array(5=>5,10=>10,20=>20,'all'=>'all'));
            $pager->setCollection($this->getCollection());
            $this->setChild('pager', $pager);
            $this->getCollection()->load();
            return $this;
        }
        public function getToolbarHtml()
        {
            return $this->getChildHtml('pager');
        }
    }

    After you did this, skip step 3 and 4 and go straight to step 5 to create .phtml. When you’re done with that you’ll get something like this:

    magento pagination

    The post Add Pagination to Custom Collection in Magento appeared first on Inchoo.

    Viewing all 263 articles
    Browse latest View live