Enabling you to develop the best web. tools and method for you. HTML ,CSS ,CODE ,websigns ,,webdevelopment
Friday, September 30, 2016
Thursday, September 29, 2016
Wednesday, September 28, 2016
Tuesday, September 27, 2016
How to Build a Striped Navigation With Flexbox
Thanks to flexbox, nowadays we can build some really attractive and modern layouts with relative ease. In this tutorial we’re going to look at an interface used recently for Google National Parks, and see how flexbox can help us improve on it.
As always, before going any further, let’s look at what we’ll be building (you may need to check out the larger version as the full effect kicks in on viewports wider than 800px):
Be sure to hover over the links to trigger the corresponding effects.
The Markup
First, let’s examine the markup we’ll be using to build this layout. We define a div element with five links within it (you can use whichever elements you feel are appropriate). Each of our links contains a div with the class of overlay. Below you can see the skeleton of our code:
<div class="flex-wrapper">
<a href="" class="one">
<div class="overlay">
<div class="overlay-inner">
<h2>Title #1</h2>
<p>Fusce vulputate orci at nulla consequat, ac tincidunt quam ultrices.</p>
</div>
</div>
</a>
<!-- four more links here -->
</div>
Initial CSS Styles
With the markup ready, we start defining some initial CSS styles, specifically:
- Specify the outermost
divas a flex container and set its height equal to the viewport height withheight: 100vh;. - Set an equal width for all links (flex items). To achieve that, we give them each
flex: 1. Additionally, all of our links will have a background image (sourced from unsplash.com) which covers the viewport height. As a fallback (in case an image isn’t available), we also specify a unique background color for each one. -
When we hover over a link, its size becomes three times bigger relative to the size of the other links. We do this by changing the value of the
flexproperty of the target link. Happily enough this property belongs to the animated CSS properties, so we’re able to generate a smooth transition effect (at least in most recent browsers).
Here’s part of the CSS code demonstrating what we’ve described above:
.flex-wrapper {
display: flex;
height: 100vh;
}
.flex-wrapper a {
position: relative;
flex: 1;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
transition: flex .4s;
}
.flex-wrapper .one {
background-image: url(IMAGE_PATH);
background-color: red;
}
.flex-wrapper a:hover {
flex: 3;
}
So far, if we preview the demo in a browser that supports flexbox, we’ll see this result:
Styling the Overlay
Our next step is to assign a few styles to the overlay. Here’s what we’ll do:
- Give overlay the same dimensions as the parent link. We can achieve this behavior by positioning it absolutely (relative to the immediate parent) and then specifying zero values for all the offset properties.
- Define the overlay as a flex container. This way, we’re able to center its direct child (i.e.
.overlay-innerelement) vertically and horizontally. - Add a semi-transparent grey background to the overlay when we hover over the parent link.
Here are the associated CSS styles:
.flex-wrapper .overlay {
display: flex;
align-items: center;
justify-content: center;
padding: 0 10px;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
transition: background-color .4s;
}
.flex-wrapper a:hover .overlay {
background-color: rgba(0, 0, 0, .5);
}
Inner Overlay
Now, it’s time to style the inner parts of our overlay. By default these are hidden and should appear only when we hover over the corresponding parent link. But not immediately, we’ll reveal them after a small delay. This delay is important; if we don’t define it specifically, the transition effect will look a little bit ugly. Remove it and test the demo to understand what I mean.
So just to recap, first the link gets bigger, then the overlay elements become visible. Also, we use the translate3d() to create some slide in effects. Last but not least, we use the transform-style: preserve-3d hack (or a similar one) to prevent possible flickering effects across different browsers.
And here are the related CSS styles:
.flex-wrapper .overlay-inner * {
visibility: hidden;
opacity: 0;
transform-style: preserve-3d;
}
.flex-wrapper .overlay h2 {
transform: translate3d(0, -60px, 0);
}
.flex-wrapper .overlay p {
transform: translate3d(0, 60px, 0);
}
.flex-wrapper a:hover .overlay-inner * {
opacity: 1;
visibility: visible;
transform: none;
transition: all .3s .3s;
}
Let’s see what that’s given us.
Going Responsive
The page looks great on large screens, but perhaps on small, or even on medium screens we’ll have to make a few adjustments. For example, here are a few things we can do:
- Flip the main-axis of the flex container by adding
flex-direction: columnto it. In doing so the flex items will flow from top to bottom. - Cancel all the transition effects and show the overlay elements by default .
Our desktop-first media query looks as follows (I’ve used 800px only because that suits the embedded demos in this post–you can choose whatever you feel is best):
@media screen and (max-width: 800px) {
.flex-wrapper {
flex-direction: column;
}
.flex-wrapper a:hover {
flex: 1;
}
.flex-wrapper a:hover .overlay {
background-color: transparent;
}
.flex-wrapper .overlay h2,
.flex-wrapper .overlay p {
opacity: 1;
visibility: visible;
transform: none;
}
}
Here’s the final appearance of our navigation:
Browser Support
The demo works in all recent browsers that support flexbox.
In some browsers you may notice that the animation of the flex property isn’t as smooth as it should be, or perhaps doesn’t work at all. For example, IE11 doesn’t animate this property, whereas Edge does. This is reasonable enough, considering the fact that flexbox is a new layout mode which is still gaining traction.
Conclusion
In this tutorial, we improved our flexbox knowledge by learning how to build a stylish navigation layout. Hopefully you enjoyed what we’ve built here and have taken some inspiration for your next projects!
If you build a similar layout, don’t forget to show us the approach (pure CSS or JavaScript-based solution) you used.
In the Wild
Before closing, I’d like to share with you a few sites which use a similar layout to what we’ve just created:
Monday, September 26, 2016
Sunday, September 25, 2016
Saturday, September 24, 2016
Friday, September 23, 2016
Thursday, September 22, 2016
Exploring Devise, Part 1
In some of my previous articles about image uploading in Rails, I made mention of Devise but did not go deep into it. In this tutorial, I will be teaching you about Devise.
Ready? Let's get started!
Devise Introduction and Modules
Devise is an authentication solution for Rails built with Warden and provided by the awesome people at Plataformatec. Devise provides different modules:
- Database Authenticatable: This encrypts and stores a password to the database to validate the authenticity of a user while signing in.
- Omniauthable: This attaches OmniAuth support to Devise. Users of your application will be able to sign in using accounts such as Facebook, Twitter, and Google.
- Confirmable: This enables the sending of emails with instructions that will help in the verification of an account.
- Recoverable: This module helps in times when users forget their password and need to recover it. With this, the user will be able to reset the password.
- Registerable: This handles the signup of users. It also allows users to edit and delete their accounts.
- Rememberable: This module makes it possible for your application to remember a logged-in user by storing a cookie.
- Trackable: This module helps track sign-in count, timestamps, and IP address.
- Timeoutable: This module is responsible for expiring a session that has not been active for a period of time.
- Validatable: With this module, email and password get to be validated.
- Lockable: This provides an extra layer of security—when activated, an account can be locked after a given number of failed sign-in attempts.
Devise Integration
For the purpose of this tutorial, we are going to generate a Rails application that we'll use to check out the workings of Devise. Let's proceed!
rails new devise-app -T
The -T flag tells Rails to generate the application without the default test suite. Navigate to your application directory and drop the following gems into your Gemfile.
#Gemfile gem 'devise', '~> 4.1' gem 'bootstrap-sass', '~> 3.3'
Now install the Devise and Bootstrap gems you just added.
bundle install
Rename your app/assets/stylesheets/application.css file to app/assets/stylesheets/application.scss and add the following lines in it:
#app/assets/stylesheets/application.scss @import "bootstrap-sprockets"; @import "bootstrap";
Open up the app/assets/javascripts/application.js file and require bootstrap-sprockets. Mine looks like this:
#app/assets/javascripts/application.js //= require jquery //= require bootstrap-sprockets //= require jquery_ujs //= require turbolinks //= require_tree .
Next, you need to run the Rails command to install the configuration files for Devise. You do so by running this command:
rails generate devise:install
The command generates the following on your terminal. You should read it to understand what happened.
create config/initializers/devise.rb
create config/locales/devise.en.yml
===============================================================================
Some setup you must do manually if you haven't yet:
1. Ensure you have defined default url options in your environments files. Here
is an example of default_url_options appropriate for a development environment
in config/environments/development.rb:
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
In production, :host should be set to the actual host of your application.
2. Ensure you have defined root_url to *something* in your config/routes.rb.
For example:
root to: "home#index"
3. Ensure you have flash messages in app/views/layouts/application.html.erb.
For example:
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
4. If you are deploying on Heroku with Rails 3.2 only, you may want to set:
config.assets.initialize_on_precompile = false
On config/application.rb forcing your application to not access the DB
or load models when precompiling your assets.
5. You can copy Devise views (for customization) to your app by running:
rails g devise:views
===============================================================================
The command also generates two files, which you can find in the config directory. It also gives us some instructions on what we should do.
Navigate to your application layout, app/views/layouts/application.html.erb, and make it look like what I have below:
#app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>DeviseApp</title>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>
</head>
<body>
<div class="container-fluid">
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
</div>
<div class="container-fluid">
<%= yield %>
</div>
</body>
</html>
You need to define the default URL options for your development environment. Add the code below in config/environments/development.rb.
#config/environments/development.rb
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
Now you need to create a User model for Devise. You can do so using your terminal.
rails generate devise User
This will generate a user.rb file in your app/models directory. The file generated will look like this:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
You can see that it contains the default modules I mentioned above. The command you ran also modified your config/routes.rb file by adding a route for devise. You should check that out.
At this point, you need to migrate your database. You do so by running:
rake db:migrate
Authentication Using Devise
Now you need to create a PagesController and wrap Devise authentication around it—this will prevent unauthorized persons from seeing the page.
rails generate controller Pages index
Open up your routes file and set the root of your application.
#config/routes.rb Rails.application.routes.draw do devise_for :users root to: "pages#index" end
Open up your PagesController and add authentication for your index and new pages.
#app/controllers/pages_controller.rb class PagesController < ApplicationController before_action :authenticate_user!, only: [:index, :new] def index end def new end end
The code shows that the index and new pages are accessible only to registered users. Open up your terminal and start your rails server. Point your browser to http://localhost:3000 and you will automatically be redirected to the Devise sign-in page.
Signing in Without Using Email
The default means of signing into Devise involves the use of email address and password. What if you want to enable users to sign in with their unique username? If that is what you want, it is possible. Let's see how.
Run the command:
rails generate migration AddUsernameToUSers username:string
This will add a new column for username in your users table. Migrate your database.
rake db:migrate
You need to add a field to your views where your users can enter their username. When you go to your app/views directory, you will not find any file that renders the Devise views. This is because Devise loads the views from its gemset. To customize it, you have to generate copies of the views. The command below does the magic.
rails generate devise:views
This will generate some folders and files in your app/views directory.
You will need to edit the page for signing in, signing up, and updating user information. Just paste the blocks of code below into their respective files.
Sign-Up
#app/views/devise/registrations/new.html.erb
<h2>Sign up</h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<div class="form-group">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :username %>
<%= f.text_field :username, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :password %>
<% if @minimum_password_length %>
<em>(<%= @minimum_password_length %> characters minimum)</em>
<% end %><br />
<%= f.password_field :password, autocomplete: "off", class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "off", class: "form-control" %>
</div>
<div class="actions">
<%= f.submit "Sign up", class: "btn btn-primary" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
Edit
#app/views/devise/registrations/edit.html.erb
<h2>Edit <%= resource_name.to_s.humanize %></h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= devise_error_messages! %>
<div class="form-group">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, class: "form-control" %>
</div>
<% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
<div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
<% end %>
<div class="form-group">
<%= f.label :username %>
<%= f.text_field :username, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
<%= f.password_field :password, autocomplete: "off", class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "off", class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
<%= f.password_field :current_password, autocomplete: "off", class: "form-control" %>
</div>
<div class="actions">
<%= f.submit "Update", class: "btn btn-primary" %>
</div>
<% end %>
<h3>Cancel my account</h3>
<p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %></p>
<%= link_to "Back", :back %>
Sign-In
#app/views/devise/sessions/new.html.erb
<h2>Log in</h2>
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<div class="form-group">
<%= f.label :username %><br />
<%= f.text_field :username, autofocus: true, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :password %><br />
<%= f.password_field :password, autocomplete: "off", class: "form-control" %>
</div>
<% if devise_mapping.rememberable? -%>
<div class="form-group">
<%= f.check_box :remember_me %>
<%= f.label :remember_me %>
</div>
<% end -%>
<div class="actions">
<%= f.submit "Log in", class: "btn btn-primary" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
Using your text editor, navigate to app/controllers/application_controller.rb. You need to modify it to permit the use of username. Modify it to look like this:
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
added_attrs = [:username, :email, :password, :password_confirmation, :remember_me]
devise_parameter_sanitizer.permit :sign_up, keys: added_attrs
devise_parameter_sanitizer.permit :account_update, keys: added_attrs
end
end
Now a user can sign in with his/her username. At this point there is something not right about your application. When a user signs in, there is no way of signing out. This does not result in a great user experience. I'll show you how to fix that.
From your terminal, create a new directory called shared in your app/views folder.
mkdir app/views/shared touch app/views/shared/_navigation.html.erb
The file you created above is a partial where the code for your navigation bar will be written. Drop in the following code.
#app/views/shared/_navigation.html.erb
<nav class="navbar navbar-inverse">
<div class="container">
<div class="navbar-header">
<%= link_to 'Tutsplus Devise', root_path, class: 'navbar-brand' %>
</div>
<div id="navbar">
<ul class="nav navbar-nav">
<li><%= link_to 'Home', root_path %></li>
</ul>
<ul class="nav navbar-nav pull-right">
<% if user_signed_in? %>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<%= current_user.name %>
<span class="caret"></span>
</a>
<ul class="dropdown-menu" role="menu">
<li><%= link_to 'Profile', edit_user_registration_path %></li>
<li><%= link_to 'Log out', destroy_user_session_path, method: :delete %></li>
</ul>
</li>
<% else %>
<li><%= link_to 'Log In', new_user_session_path %></li>
<li><%= link_to 'Sign Up', new_user_registration_path %></li>
<% end %>
</ul>
</div>
</div>
</nav>
Now you need to render the navigation bar in your application layout. Open up app/views/layouts/application.html.erb and drop in the code to render your navigation bar.
#app/views/layouts/application.html.erb ... <div class="container-fluid"> <%= render "shared/navigation" %> <p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p> </div> ...
Conclusion
In this part you learned how to install Devise and add authentication to your pages. I also made mention of a partial. I will cover that in a separate tutorial.
In the next part, we will cover some areas more advanced than this. I hope this was worth your time!
CSS Grid Layout: Fluid Columns and Better Gutters
In this tutorial we’re going to take the grid from our previous tutorial and use it as a playground to explore different units of measurement and Grid’s properties.
Flexible Units
The whole point of Grid is to enable us to properly control layout on the web, so let’s make our static grid fluid before we go any further. If you recall, we were defining our grid with static pixel measurements:
grid-template-columns: 150px 20px 150px 20px 150px; grid-template-rows: auto 20px auto 20px auto;
It’s quite possible to use other units here too, such as ems, or rems for example. Or even more unusual units like vh and vmin.
In this case we’re going to change our pixel units for percentages. As we’re defining our column widths and our gutter widths manually we have to make sure the total values equal 100%:
grid-template-columns: 30% 5% 30% 5% 30%;
Mind the Gap
There does exist a newer, more efficient way to declare gutter measurements, and that’s with a purpose-made property. We can use grid-column-gap and grid-row-gap, or the shorthand grid-gap property instead.
Using this means we no longer need to include grid tracks to accommodate our gutters, we only need to declare the columns and rows, so in our case that means three of each:
grid-template-columns: 33.33% 33.33% 33.33%; grid-template-rows: auto auto auto;
Hmm, that’s a bit messy, but it sort of does the job. The difference you’ll notice is that the column widths now add up to 100%; our gutters will actually be subtracted from them automatically.
Repeat
Let’s write this is a neater way, using the repeat() function:
grid-template-columns: repeat(3, 33.33%);
This states “repeat 33.33% three times” giving us three columns. And we don’t even need the grid-template-rows declaration because the auto value is assigned as default anyway. Now we just need to state how big we want our gutters:
grid-template-columns: repeat(3, 33.33%); grid-gap: 20px;
grid-gap will accept fixed or flexible units, which means we can perfectly combine fluid columns and fixed gutters without any complex calculations on our part.
Resetting Our grid-column and grid-row Values
Something is amiss: we still have a load of grid-column and grid-row declarations on our grid items, but they’re all wrong because we no longer have as many grid tracks. Happily, because we’re using grid-gap to define our gutters, we can leave positioning of the items to automatic placement–items will no longer fall into the gutters. For now, remove all the positioning:
The fr Unit
One final improvement can be made to our simple grid; we’re going to introduce the fr, or fraction unit. A single fr unit describes “one piece of however many pieces we’re splitting this into”. For example, we could declare our columns using:
grid-template-columns: 1fr 1fr 1fr;
Here there’s a total of three fr units, so each column would be one third wide. Here’s another example:
grid-template-columns: 2fr 1fr 1fr
Now there’s a total of four fr units, so the first column would take up half the available width, with the other two columns each taking a quarter.
These units are really powerful, especially in combination with other units of measurement:
grid-template-columns: 300px 1fr 3fr 20%;
Here we’ve declared four columns:
- the first is fixed at 300px wide
- the last is a flexible 20% of the grid container element wide
- then the fr units are calculated, leaving the second column with one piece of the remaining space
- and the third with three pieces.
It looks like this, perfectly highlighting auto-placement as our nine items shift to fill the gaps:
But back to our original grid. Let’s replace the clumsy and inaccurate 33.33% value with 1fr:
grid-template-columns: repeat(3, 1fr);
Finished pen:
Conclusion
So, to recap:
- Grid will accept flexible units in combination with fixed units of measurements.
- We needn’t declare gutters within our
grid-template, we can use thegrid-gapproperties instead. grid-gapmeans we are less bound to positioning our grid items–we can hand more responsibility over to auto-placement.- The
repeat()function will save us time and keep our stylesheets maintainable. - The fr, or fraction unit is a very powerful way of describing grid templates.
We’ve come a long way in just two tutorials, but you’re now the proud owner of a very neat and concise grid! In the next tutorial we’ll explore grid areas, taking a look at the span keyword and some practical layouts.
Useful Resources
- The
<fraction>type andfrunit on W3C - Again, follow @rachelandrew
Wednesday, September 21, 2016
Tuesday, September 20, 2016
Monday, September 19, 2016
Sunday, September 18, 2016
Bearded_Foodies You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
Bearded_Foodies You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
Mrsyedwaseem You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
Mrsyedwaseem You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
celestegau83 You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
celestegau83 You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
ewposters You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
ewposters You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
vinn_oliveira You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
vinn_oliveira You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
lavaneradi You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
lavaneradi You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
Yssa_xoxo You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
Yssa_xoxo You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
GrafficT You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
GrafficT You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
menmybrand You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
menmybrand You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
mlsitess You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
mlsitess You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
siddhshanidham You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
siddhshanidham You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
SheannSheann You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
SheannSheann You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
RubyLibHunt You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
RubyLibHunt You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
pdamindanuwan You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
pdamindanuwan You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
anamartinez_lan You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
anamartinez_lan You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
MedAbderrahim12 You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
MedAbderrahim12 You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
Huang_Cloud001 You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
Huang_Cloud001 You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
Jean07za You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
Jean07za You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
Lalithgc You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
Lalithgc You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
_IDNews You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
_IDNews You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
Oseiphilip125 You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
Oseiphilip125 You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
narendrasawalk8 You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
narendrasawalk8 You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
felicitysofts You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
felicitysofts You’re smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
RT TheEconomist: The dangerous rise of antibiotic resistance will lead to the resurgence of old diseases … https://t.co/gQdXPrazT8
RT TheEconomist: The dangerous rise of antibiotic resistance will lead to the resurgence of old diseases … http://pic.twitter.com/gQdXPrazT8
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
RT Global_Policy: Future Demagogues? Powerful Individuals in a Globalized World #Zuckerberg #Schmidt #Musk #Kalani… https://t.co/m4gAYRS048
RT Global_Policy: Future Demagogues? Powerful Individuals in a Globalized World #Zuckerberg #Schmidt #Musk #Kalani… https://t.co/m4gAYRS048
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
RT michaeldweiss: Compensation payments same news cycle as UN confirms Assad gassing people is inconvenience to th… https://t.co/jROcZv2NE8
RT michaeldweiss: Compensation payments same news cycle as UN confirms Assad gassing people is inconvenience to th… http://pic.twitter.com/jROcZv2NE8
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
RT irinnews: The refugee crisis in Europe has largely disappeared from our screens, but it isn't over: … https://t.co/CeoueFkEMI
RT irinnews: The refugee crisis in Europe has largely disappeared from our screens, but it isn't over:
— Web devel0pment (@devwebbest) September 18, 2016
… http://pic.twitter.com/CeoueFkEMI
from Twitter https://twitter.com/devwebbest
RT LSEReviewBooks: 'Bursting with memorable anecdotes, intriguing detail and splashes of colour' … https://t.co/r1lPwNJame
RT LSEReviewBooks: 'Bursting with memorable anecdotes, intriguing detail and splashes of colour' … http://pic.twitter.com/r1lPwNJame
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
RT TheStudyofWar: Syrian Military Begins to Withdraw From Vital Aleppo Road - ABC News - https://t.co/ieZybJlujW … https://t.co/PP96kV3p4F
RT TheStudyofWar: Syrian Military Begins to Withdraw From Vital Aleppo Road - ABC News - https://t.co/ieZybJlujW … http://pic.twitter.com/PP96kV3p4F
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
RT BenParker140: How Europe is splitting the bill on refugees: https://t.co/UHrjh1sScD https://t.co/H7zQ5NJ0VU
RT BenParker140: How Europe is splitting the bill on refugees: https://t.co/UHrjh1sScD http://pic.twitter.com/H7zQ5NJ0VU
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
RT ipinst: "Peace cuts across every global issue"- #IPI's hillarysaviello at #PeaceDay Youth Summit- … https://t.co/2OZEmQou58
RT ipinst: "Peace cuts across every global issue"- #IPI's hillarysaviello at #PeaceDay Youth Summit- … http://pic.twitter.com/2OZEmQou58
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
RT issafrica: Top view | UN #DemocracyDay: Africa has little to celebrate. https://t.co/Tod2Umq8Jl https://t.co/vwNAlPnWHZ
RT issafrica: Top view | UN #DemocracyDay: Africa has little to celebrate. https://t.co/Tod2Umq8Jl http://pic.twitter.com/vwNAlPnWHZ
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
RT CrisisGroup: Any sustainable solution to the global refugee crisis must address conflict prevention https://t.co/hmd4JjFwi2 #UN4Refugee…
RT CrisisGroup: Any sustainable solution to the global refugee crisis must address conflict prevention https://t.co/hmd4JjFwi2 #UN4Refugee…
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
RT issafrica: Top read | #Zimbabwe's reforms: An exercise in credibility - or pretence? https://t.co/twCGjlPBbG https://t.co/foZK2pP3xF
RT issafrica: Top read | #Zimbabwe's reforms: An exercise in credibility - or pretence? https://t.co/twCGjlPBbG http://pic.twitter.com/foZK2pP3xF
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
RT GlobPeaceIndex: POSITIVE PEACE: How can we establish peacefulness at home and around the world? #peace https://t.co/oAhv6uaORa
RT GlobPeaceIndex: POSITIVE PEACE: How can we establish peacefulness at home and around the world? #peace http://pic.twitter.com/oAhv6uaORa
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
RT issafrica: Top read | #Africa has suffered a number of blows against #democracy. https://t.co/Tod2Umq8Jl https://t.co/RPZa2Fu6DD
RT issafrica: Top read | #Africa has suffered a number of blows against #democracy. https://t.co/Tod2Umq8Jl http://pic.twitter.com/RPZa2Fu6DD
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
RT Carnegie_Europe: Ahead of tomorrow's #UN4RefugeesMigrants, check out Carnegie's best analysis on #migration: … https://t.co/1hxBu4w864
RT Carnegie_Europe: Ahead of tomorrow's #UN4RefugeesMigrants, check out Carnegie's best analysis on #migration: … http://pic.twitter.com/1hxBu4w864
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
RT TheStudyofWar: .karendeyoung1 quotes GEN Votel from our conference-"Trust deficit w/#Russia." #ISWInsight. … https://t.co/lUivjceMlE
RT TheStudyofWar: .karendeyoung1 quotes GEN Votel from our conference-"Trust deficit w/#Russia." #ISWInsight. … http://pic.twitter.com/lUivjceMlE
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
RT WarOnTheRocks: Do you think the CIA knew about the Turkish coup attempt ahead of time? Then read about this https://t.co/bIXr9W9LJx
RT WarOnTheRocks: Do you think the CIA knew about the Turkish coup attempt ahead of time? Then read about this https://t.co/bIXr9W9LJx
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
RT keohanedan: The remaining 27 can no longer blame the UK for any lack of progress on EU defense policy. … https://t.co/NMdqbAW5K0
RT keohanedan: The remaining 27 can no longer blame the UK for any lack of progress on EU defense policy. … http://pic.twitter.com/NMdqbAW5K0
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
Saturday, September 17, 2016
velasqueziris60 You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
velasqueziris60 You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
zovlqa You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
zovlqa You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
sweetrevengebtq You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
sweetrevengebtq You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
shawneks1 You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
shawneks1 You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
masterkeyhelene You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
masterkeyhelene You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
projx2go You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
projx2go You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
Super_johnmike You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
Super_johnmike You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
shevenszheng You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
shevenszheng You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
GwynethCheever You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
GwynethCheever You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
danthonyl You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
danthonyl You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 18, 2016
from Twitter https://twitter.com/devwebbest
⌘ #RWD websites w/ a strong point of view: https://t.co/lnoPOmAtvj https://t.co/PrdFH7Ez5k … https://t.co/OoDOVdLXCs
⌘ #RWD websites w/ a strong point of view:https://t.co/lnoPOmAtvjhttps://t.co/PrdFH7Ez5k
— Web devel0pment (@devwebbest) September 17, 2016
… http://pic.twitter.com/OoDOVdLXCs
from Twitter https://twitter.com/devwebbest
WhyNotDoStuff Thanks Alec, what a lovely example!
WhyNotDoStuff Thanks Alec, what a lovely example!
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
sarah_edo Please keep me posted ;-)
sarah_edo Please keep me posted ;-)
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
Trying desperately to find a landing page which doesn’t use Parallax, but rather quirky layouts and illustrations. And failing.
Trying desperately to find a landing page which doesn’t use Parallax, but rather quirky layouts and illustrations.
— Web devel0pment (@devwebbest) September 17, 2016
And failing.
from Twitter https://twitter.com/devwebbest
A fast, responsive search/filters interface done really well. NYC Schoolfinder. https://t.co/2k2VhHKWM6 #rwd https://t.co/fPxweg066U
A fast, responsive search/filters interface done really well. NYC Schoolfinder. https://t.co/2k2VhHKWM6 #rwd http://pic.twitter.com/fPxweg066U
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
Stuurmen by StuurmenFrank - Featured cssawards https://t.co/widEAOmBd7 #webdesign #css https://t.co/ivjYJy5scg
Stuurmen by StuurmenFrank - Featured cssawards https://t.co/widEAOmBd7 #webdesign #css http://pic.twitter.com/ivjYJy5scg
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
GIMEZ by awdagency - #SOTD cssawards https://t.co/6OWnyz40WE #webdesign #onepage https://t.co/PmANb8JlvD
GIMEZ by awdagency - #SOTD cssawards https://t.co/6OWnyz40WE #webdesign #onepage http://pic.twitter.com/PmANb8JlvD
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
React Container Query for container queries with, well, React. https://t.co/njLi7XSCGM
React Container Query for container queries with, well, React. https://t.co/njLi7XSCGM
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
Emoji Printer, for creating Emoji-based posters. notwaldorf would approve. https://t.co/Rez7rDkjkz (via jvegis) https://t.co/Jy3kCKynPJ
Emoji Printer, for creating Emoji-based posters. notwaldorf would approve. https://t.co/Rez7rDkjkz (via jvegis) http://pic.twitter.com/Jy3kCKynPJ
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
sebarh Thanks Sebastian!
sebarh Thanks Sebastian!
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
RT RWD: Need to demo/test a locally-built site on various devices? https://t.co/7XFhYww5Uw looks like a great opt… https://t.co/vx4SNiokOi)
RT RWD: Need to demo/test a locally-built site on various devices? https://t.co/7XFhYww5Uw looks like a great opt… https://t.co/vx4SNiokOi)
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
JayUny It doesn’t count! :-)
JayUny It doesn’t count! :-)
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
Any suggestions for responsive designs in Japan, Russia or South America? URLs are welcome!
Any suggestions for responsive designs in Japan, Russia or South America? URLs are welcome!
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
What are some of the most memorable websites you’ve seen lately? (Responsive, art-directed, done well?) #rwd
What are some of the most memorable websites you’ve seen lately? (Responsive, art-directed, done well?) #rwd
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
RT RWD: Ever wanted to display the right mobile keyboard for numeric inputs? filamentgroup has you covered: https://t.co/2fDBWnc3zo
RT RWD: Ever wanted to display the right mobile keyboard for numeric inputs? filamentgroup has you covered: https://t.co/2fDBWnc3zo
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
RT lukew: links matter https://t.co/AYt7BIr0Si
RT lukew: links matter http://pic.twitter.com/AYt7BIr0Si
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
ebrunborg Arriving on Sunday. Evening dinner? ;-)
ebrunborg Arriving on Sunday. Evening dinner? ;-)
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
Don't mind the URL. A[dult] swim singles 2016. 25 weeks, 25 tracks. Visualized. Well done. https://t.co/W4mgtVVWIX https://t.co/XO3yyRIdPj
Don't mind the URL. A[dult] swim singles 2016. 25 weeks, 25 tracks. Visualized. Well done. https://t.co/W4mgtVVWIX http://pic.twitter.com/XO3yyRIdPj
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
Good weekend reading: On Onboarding (and a few other books). Email/sharing is required. https://t.co/Jd6VAVJNCK https://t.co/APdd6ra49Q
Good weekend reading: On Onboarding (and a few other books). Email/sharing is required. https://t.co/Jd6VAVJNCK http://pic.twitter.com/APdd6ra49Q
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
Articles on #security-related, #InternationalRelations (IR) & #HumanSecurity that are evergreen and global in sco… https://t.co/mfJA3Jb5QG
Articles on #security-related, #InternationalRelations (IR) & #HumanSecurity that are evergreen and global in sco… https://t.co/mfJA3Jb5QG
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
Friday, September 16, 2016
NCPtour_travel You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
NCPtour_travel You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
happyesthete You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
happyesthete You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
onebusinessguru You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
onebusinessguru You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
odf_group You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
odf_group You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
greatdigitsol You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
greatdigitsol You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
ktfarnan You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
ktfarnan You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
LJBPR You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
LJBPR You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
Porkchops_tees You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
Porkchops_tees You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
smartstitching3 You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
smartstitching3 You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
OnesTopAppSol You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
OnesTopAppSol You’re quite smashing! ;) Why not subscribe to our newsletter, with handy tips'n'tricks (1-click)? https://t.co/BNh5FgpB9j
— Web devel0pment (@devwebbest) September 17, 2016
from Twitter https://twitter.com/devwebbest
Malarkey snookca Good point, Andy! indysigner? ;-)
Malarkey snookca Good point, Andy! indysigner? ;-)
— Web devel0pment (@devwebbest) September 16, 2016
from Twitter https://twitter.com/devwebbest
Do #water-centered #US NGOs need a global Water Grand Strategy? PeterEngelke1 & P Michel say yes https://t.co/phAWktRqs7 AtlanticCouncil
Do #water-centered #US NGOs need a global Water Grand Strategy? PeterEngelke1 & P Michel say yeshttps://t.co/phAWktRqs7 AtlanticCouncil
— Web devel0pment (@devwebbest) September 16, 2016
from Twitter https://twitter.com/devwebbest
Have you switched to HTTP/2 yet? If not, why not?
Have you switched to HTTP/2 yet? If not, why not?
— Web devel0pment (@devwebbest) September 16, 2016
from Twitter https://twitter.com/devwebbest
✎ It’s Friday, so let’s... draw! Some hand lettering inspiration on smashingmag. https://t.co/uURu1z2wCw https://t.co/4y86woGPsZ
✎ It’s Friday, so let’s... draw! Some hand lettering inspiration on smashingmag. https://t.co/uURu1z2wCw http://pic.twitter.com/4y86woGPsZ
— Web devel0pment (@devwebbest) September 16, 2016
from Twitter https://twitter.com/devwebbest
What #counterterrorism & #IntelligenceGathering developments have occurred since 9/11? See here https://t.co/krMnBF532C cc CFR_org
What #counterterrorism & #IntelligenceGathering developments have occurred since 9/11? See here https://t.co/krMnBF532C cc CFR_org
— Web devel0pment (@devwebbest) September 16, 2016
from Twitter https://twitter.com/devwebbest
Losing Sirte was far from a fatal blow to the #Islamic State in #Libya, says the CSS’ Lisa Watanabe https://t.co/Mb3QOF6wEi #Terrorism
Losing Sirte was far from a fatal blow to the #Islamic State in #Libya, says the CSS’ Lisa Watanabe https://t.co/Mb3QOF6wEi #Terrorism
— Web devel0pment (@devwebbest) September 16, 2016
from Twitter https://twitter.com/devwebbest
The Enhanced Script Notifications in OpenCart 2.2.x.x
Today, we’re going to discuss one of the coolest features of OpenCart—script notifications. Although they've been available since the release of OpenCart 2.x version, they've been enhanced in the recent release of OpenCart 2.2.x.x. Throughout the course of this tutorial, we’ll discuss the concept and demonstrate it by creating a working module.
Introduction
As a developer, you'll often need to alter the flow of the core framework behaviour, whether it’s to add a new feature to the framework or to enhance the existing workflow. In either case, you need to have control over the workflow so that you can hook into the functionality and achieve the desired behavior.
Let’s try to understand it by a simple example. If you’re running a popular online store, chances are that the most popular products of your store will be sold out soon. In this case, it would be nice to get a notification about it and take the proper action accordingly.
So what you could do in the aforementioned example is to look for the method that's called when the order is placed. Next, you can hook into the order creation flow so that you can control when the order is placed. That allows you to execute a random piece of code in the context of the called event. That's where event notifications come into the picture, allowing us run arbitrary code and modify the resulting outcome.
What Are Script Notifications?
As per the official OpenCart documentation:
In 2.2+ we have added a new events system, these are hooks that can be called before and after events such as a controller method call, model method call or templates being loaded. They give the ability to manipulate the input method parameters and returned output.
That's really powerful, as you could literally hook into any method call and change the output returned by that method. However, you should be careful during the implementation of hooks, if you're taking the responsibility of modifying the resulting output of the call. That's because if your implementation returns any value, it will stop any other event actions that are set to be called.
What does it take to implement script notifications? Here are the necessary steps:
Register Your Event
When you're registering your event with OpenCart, you're telling OpenCart that you want to monitor xyz action, and if that happens please execute the foo action in the bar controller file. While doing so, you have two options to choose from—before and after.
If you choose the before option, OpenCart will run the foo action before the action that's being monitored, and in the case of the after option it'll execute foo after the action. In the case of the before option, it's important to note that if the foo method returns anything, OpenCart won't execute the original method code that's being monitored at all.
Later in this article, we'll see how to register an event during custom module implementation.
Implement the Target Action
Next, you'll need to implement the controller and the corresponding action method that's been associated with an event in the first step. That's the place where your custom code should go in. OpenCart sends context-specific arguments to your foo method, so that you could use it if needed.
So that’s just a glimpse of what script notifications are capable of.
Do We Still Need OCMOD?
If you're familiar with either vQmod or OCMOD, chances are that you're going to roll up your sleeves against the event notifications feature. After all, you could achieve everything by the aforementioned goodies that you would have anyway accomplished with event notifications.
For those who are not familiar with either vQmod or OCMOD, listed below are nice introductory articles that explain it.
So, the question is, if you could use either vQmod or OCMOD to alter the core files, why would you use script notifications? It’s because of the fact that there may be multiple OCMOD plugins trying to alter the same code, and that could result in conflict. In the case of events, there’s no chance of conflict as each event is executed in a specific order.
So the conclusion is that if it’s possible to use events, go for that. On the other hand, if you feel that there’s no event available for your use-case, it’s completely fine to go with OCMOD.
Create a Back-End Module
In this section, we're going to build a module that demonstrates how to use event notifications in OpenCart. I assume that you're aware of the basic module development process in OpenCart, as we'll skid through the basics. If you're not aware of it, here's a nice article that you can go through quickly.
Let's understand the use-case before we go ahead and implement it. Assume that you have a store that fetches the catalog information from the third-party API back-end. You've set up a cron that regularly fetches the information and inserts it in the OpenCart database, so that it can be displayed in the front-end. Although you've set up a cron that updates the information at regular intervals, you want to make sure that the information displayed in the front-end must be in real time.
In the front-end, OpenCart calls the getProduct function when it displays the product detail page. So, that's the ideal candidate for you to hook into and update the database if the API information has changed.
So let's start creating the back-end files first.
Go ahead and create the admin/controller/module/demoevent.php file with the following contents.
<?php
// admin/controller/module/demoevent.php
class ControllerModuleDemoevent extends Controller {
private $error = array();
public function install() {
$this->load->model('extension/event');
$this->model_extension_event->addEvent('event_product_info', 'catalog/model/catalog/product/getProduct/before', 'custom/product/info');
}
public function uninstall() {
$this->load->model('extension/event');
$this->model_extension_event->deleteEvent('event_product_info');
}
}
As we discussed earlier, the first thing you would like to do is the registration of the event. It's the install hook that we'll use to do it, as it'll be executed when the module is installed.
We've used the addEvent method of the event model under the extension directory, and that method has three arguments.
The first argument, event_product_info, is the name of the event. You could name it anything, but make sure that it's unique.
The second argument is very important, and it points out to the method that you would like to hook into. In our case, we have to choose the getProduct method of the Product Model under the Catalog directory. So the hierarchy is model/catalog/product/getProduct. In addition to that, it should be prefixed by either catalog or admin, which stands for the application. In our case, it's catalog as we're hooking into the front-end method. Finally, it'll be post-fixed by either before or after, which tells OpenCart to run our code accordingly.
The third argument points to the action of your controller that will be triggered when the event is fired. It's set to custom/product/info, so you'll have to create a Product controller with the info method under the custom directory, as we'll do in a moment.
Finally, the uninstall method is used to remove the event during the uninstallation of the module.
Next, let's create a language file for our custom module at admin/language/en-gb/module/demoevent.php with the following contents.
<?php // admin/language/en-gb/module/demoevent.php // Heading $_['heading_title'] = 'Custom Event (Script Notification) Demo Module';
We've finished with the back-end file setup. Now, you should be able to see our module listed under Extensions > Modules in the back-end of OpenCart. Go ahead and install it. Now, we'll go ahead and create the files in the front-end.
Create a model file catalog/model/custom/product.php with the following contents. It's a typical model file as per the OpenCart structure.
<?php
// catalog/model/custom/product.php
class ModelCustomProduct extends Model {
function getProductLastModifiedInfo($product_id) {
$query = $this->db->query("SELECT date_modified FROM " . DB_PREFIX . "product WHERE product_id = '" . (int)$product_id . "'");
return $query->row;
}
function updateProductInfo($product_id, $data) {
include_once __DIR__.'../../../admin/model/catalog/product.php';
$modelCatalogProduct = new ModelCatalogProduct();
$modelCatalogProduct->editProduct($product_id, $data);
}
}
It implements two important methods. The getProductLastModifiedInfo method is used to fetch the last modified date of the product, and the updateProductInfo method is used to update the product information in the database. In a moment, we'll see how these methods will be used.
Finally, let's create one of the most important files of this article, catalog/controller/custom/product.php, with the following contents. It's a controller file that implements the info action method that is set to be called when the getProduct method of the Product Model is called.
<?php
// catalog/controller/custom/product.php
class ControllerCustomProduct extends Controller {
public function info($route, $product_id) {
// fetch product info from API (psudo code)
$product_api_info = api_call_to_fetch_product_info($product_id);
// fetch product info from database
$this->load->model('custom/product');
$product_modified_date = $this->model_custom_product->getProductLastModifiedInfo($product_id);
if ($product_modified_date['date_modified'] != $product_api_info['date_modified']) {
// api info has changed so first let's update db
// Note: you probably want to format $product_api_info as per the format required by editProduct method
$this->model_custom_product->updateProductInfo($product_id, $product_api_info);
// return latest/real-time product information
return array(
'product_id' => $product_api_info['product_id'],
'name' => $product_api_info['name'],
'description' => $product_api_info['description'],
'meta_title' => $product_api_info['meta_title'],
'meta_description' => $product_api_info['meta_description'],
'meta_keyword' => $product_api_info['meta_keyword'],
'tag' => $product_api_info['tag'],
'model' => $product_api_info['model'],
'sku' => $product_api_info['sku'],
'upc' => $product_api_info['upc'],
'ean' => $product_api_info['ean'],
'jan' => $product_api_info['jan'],
'isbn' => $product_api_info['isbn'],
'mpn' => $product_api_info['mpn'],
'location' => $product_api_info['location'],
'quantity' => $product_api_info['quantity'],
'stock_status' => $product_api_info['stock_status'],
'image' => $product_api_info['image'],
'manufacturer_id' => $product_api_info['manufacturer_id'],
'manufacturer' => $product_api_info['manufacturer'],
'price' => ($product_api_info['discount'] ? $product_api_info['discount'] : $product_api_info['price']),
'special' => $product_api_info['special'],
'reward' => $product_api_info['reward'],
'points' => $product_api_info['points'],
'tax_class_id' => $product_api_info['tax_class_id'],
'date_available' => $product_api_info['date_available'],
'weight' => $product_api_info['weight'],
'weight_class_id' => $product_api_info['weight_class_id'],
'length' => $product_api_info['length'],
'width' => $product_api_info['width'],
'height' => $product_api_info['height'],
'length_class_id' => $product_api_info['length_class_id'],
'subtract' => $product_api_info['subtract'],
'rating' => round($product_api_info['rating']),
'reviews' => $product_api_info['reviews'] ? $product_api_info['reviews'] : 0,
'minimum' => $product_api_info['minimum'],
'sort_order' => $product_api_info['sort_order'],
'status' => $product_api_info['status'],
'date_added' => $product_api_info['date_added'],
'date_modified' => $product_api_info['date_modified'],
'viewed' => $product_api_info['viewed']
);
}
}
}
The important thing to note here is that OpenCart provides context-specific arguments to your method. The first $route argument tells you the route associated with the event that has been triggered. In our case, it should be catalog/product/getProduct. The second argument is the product id as it's a required argument by the actual getProduct method.
The logic is something like this: we'll compare the modified date of the product in the OpenCart database with the information that's returned by the API callback.
So the first step is to load the real-time product information from the API. Please note that the api_call_to_fetch_product_info method is just a dummy method that you would like to replace with your actual API call.
Next, we use the getProductLastModifiedInfo method, which was created in the earlier section, to fetch the modified date of the product from the OpenCart database.
Finally, we do the comparison, and if the date differs, it'll update the OpenCart database with the latest product information using the updateProductInfo method in our Model class.
We've also returned the product information in array format in the same way the actual getProduct would have returned. It's important to note that as we've provided the return value in our hook, it won't call any further event calls including the call to the original getProduct method as well.
So, in this way you could influence the execution flow in OpenCart. It's a really powerful way to alter the core functionality. However, you should be careful while approaching this solution as it gives you total control to change the resulting output of any method.
Conclusion
Today, we've discussed one of the exciting features in OpenCart called script notifications. We started with an introduction to the subject, and it was the latter half of the article that demonstrated how to use them by building a custom event module.
As always, if you're looking for additional OpenCart tools, utilities, extensions, and so on that you can leverage in your own projects or for your own education, don't forget to see what we have available in the marketplace.
Queries and comments are always welcome!