tag:blogger.com,1999:blog-91862522119393140922024-02-07T19:36:45.561-08:00Creative Technologyfind / learn / shareAnonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.comBlogger82125tag:blogger.com,1999:blog-9186252211939314092.post-64933430855047575752017-11-28T19:13:00.002-08:002017-12-18T09:11:19.410-08:00Generate and host static sites using Angular Universal and Amazon S3<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTRIMEbZmXFchHwMl8iciBjb7R__lzQDg-5YSEbcRrQgvz5iCb3ue6oVpf_px1OD-jPTtqEBuaaj6LTfk1YmuoLiHZiFNtwyCKIzNs07-Vfr06c2UH-4kRb7XxUscGKfsENHhRnkSk6vQ/s1600/amazon.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="900" data-original-width="1600" height="112" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTRIMEbZmXFchHwMl8iciBjb7R__lzQDg-5YSEbcRrQgvz5iCb3ue6oVpf_px1OD-jPTtqEBuaaj6LTfk1YmuoLiHZiFNtwyCKIzNs07-Vfr06c2UH-4kRb7XxUscGKfsENHhRnkSk6vQ/s200/amazon.png" width="200" /></a></div>
In this post we look at how Angular Universal and Amazon S3 can be used to generate and host a static site. We also will look at how to support pages which have not been statically generated, but exist via an API.<br />
<br />
<a name='more'></a><br />
One best practice in web architecture is to separate your backend API from the frontend, which has several advantages:<br />
<br />
<ul style="text-align: left;">
<li>True separation of code and services</li>
<li>APIs can have a full suite of tests</li>
<li>APIs can be micro-services and hosted/scaled separately</li>
<li>Frontend can be static and hosted on cheaper static hosting</li>
<li>Frontend can define the CMS views or have an integrated UI</li>
<li>Frontend can utilize API versioning to lock releases</li>
</ul>
<br />
<br />
However there are also disadvantages to this architecture approach. One huge drawback with a traditional static frontend approach is that content created via an API is not available for AMP/fast load and SEO optimization.<br />
<br />
It has been possible to workaround this limitation by having your backend CMS dynamically generate and deploy your static site, after each change is made in the CMS. This is heavy and requires tightly coupled frontend and backend code.<br />
<br />
Something has since changed, we now have a great fullstack solution which moves the static generation to the frontend where really it should be. It also uncouples frontend code from backend CMS logic.<br />
<br />
<a href="https://universal.angular.io/">Angular Universal</a> is a set of modules which compile your Angular TypeScript code into templates for serving either dynamically from a NodeJS server, or as static html templates. Once the static page has loaded, it will then load the JavaScript version on-top and update any content in realtime.<br />
<br />
This approach means we can write our code once and support multiple delivery scenarios. Amazing!<br />
<br />
<br />
<b>1) Starting an Angular Universal project:</b><br />
<br />
A quick starting point is to clone the official <a href="https://github.com/angular/universal-starter">universal-starter repository</a>:<br />
<br />
<pre class="brush:js">git clone https://github.com/angular/universal-starter
</pre>
<br />
Install the dependencies and run the project locally:<br />
<br />
<pre class="brush:js">npm install
npm run start
</pre>
<br />
To generate the static version, simply run:<br />
<br />
<pre class="brush:js">npm run build:prerender && npm run serve:prerender
</pre>
<br />
You can view the generated static site in the dist/browser folder.<br />
<br />
Very useful, but let's push it further by including page title tags and ajax data rendered within the same static templates.<br />
<br />
<br />
<b>2) Adding title and meta tags:</b><br />
<br />
Start by importing the Meta and Title modules into your app/home/home.component.ts file:<br />
<br />
<pre class="brush:js">import { Meta, Title } from '@angular/platform-browser';
</pre>
<br />
Then add them to the constructor:<br />
<br />
<pre class="brush:js">constructor(
private title: Title,
private meta: Meta
) {}
</pre>
<br />
Now you can set your title and meta tags using:<br />
<br />
<pre class="brush:js">ngOnInit() {
this.message = 'Hello';
this.title.setTitle('Hello World');
this.meta.updateTag({ name: 'description', content: 'Hello World description!' });
}
</pre>
<br />
Run it locally and try generating the static version to see it working!<br />
<br />
<br />
<b>3) Loading dynamic content from an API</b><br />
<br />
Now lets add some ajax content to the app/lazy/lazy.module.ts file:<br />
<br />
<pre class="brush:js">import { HttpClient, HttpClientModule, HttpErrorResponse } from '@angular/common/http';
</pre>
<br />
Then add the properties to the Class:<br />
<br />
<pre class="brush:js">posts:any[];
constructor(private http: HttpClient) { }
</pre>
<br />
Add the http get request:<br />
<br />
<pre class="brush:js">ngOnInit() {
const getPosts = this.http.get('https://jsonplaceholder.typicode.com/posts')
.catch((error: HttpErrorResponse) => Observable.throw(error));
getPosts.subscribe(res => this.posts = res)
}
</pre>
<br />
And finally output the post data to the page:<br />
<br />
<pre class="brush:js">@Component({
selector: 'lazy-view',
template: `<h3>i'm lazy</h3><pre>{{posts | json}}</pre>`
})
</pre>
<br />
Now run again locally and with static gen to see the json data loaded correctly into the static page!<br />
<br />
<br />
<b>4) Static generation for S3 buckets</b><br />
<br />
We want to host the statically generated version somewhere in the cloud. Google AppEngine is a good option, but i'm deciding to use Amazon S3 for it's ease and cost.<br />
<br />
Since Amazon S3 buckets always contain a folder path in their url, generating the static site the regular way with a root / will cause issues. We need to pass a base href through to the build command. Unfortunately because the way the Angular Universal commands are chained, it's not possible to pass through the base url in one simple command.<br />
<br />
Use the following commands one after another, and change the XX placeholder to match your S3 bucket address:<br />
<br />
<pre class="brush:js">ng build --prod --base-href http://XX.s3-website-us-east-1.amazonaws.com
ng build --prod --app 1 --output-hashing=false --base-href http://XX.s3-website-us-east-1.amazonaws.com
npm run webpack:server && npm run generate:prerender
</pre>
Zip the files at dist/browser and upload the an S3 bucket. Ensure you set the file permissions to 'Public'.<br />
<br />
Also change the options to 'Hosting a static site' and set the index and error files to be index.html<br />
<br />
<br />
<b>5) Supporting static and dynamic routes on S3</b><br />
<br />
Within the S3 bucket options, add a redirect rule which detects if a file is missing.<br />
<br />
<pre class="brush:js">
<RoutingRules>
<RoutingRule>
<Condition>
<HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
</Condition>
<Redirect>
<ReplaceKeyPrefixWith>#!/</ReplaceKeyPrefixWith>
</Redirect>
</RoutingRule>
</RoutingRules>
</pre>
<br />
It captures the path and passes it to JavaScript using a hashbang url. This means if a user navigates to /posts/234 and the static file was not generated, or does not exist, it will redirect to /!#/posts/234<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1A5OIcgIbOyq6Ulfoid1Oa5AxrRUKBG0-rH8zTr1ignRLsOete0laWt8qdiKcR60DhPR0srjTjk7K8ges7hw_gJdxK_yqciIemy9oIFMmJLao1cPbZ0mE9Ci456mOz3n_GK4LYDQ3Izw/s1600/Screen+Shot+2017-11-28+at+10.02.01+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1196" data-original-width="1078" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1A5OIcgIbOyq6Ulfoid1Oa5AxrRUKBG0-rH8zTr1ignRLsOete0laWt8qdiKcR60DhPR0srjTjk7K8ges7hw_gJdxK_yqciIemy9oIFMmJLao1cPbZ0mE9Ci456mOz3n_GK4LYDQ3Izw/s320/Screen+Shot+2017-11-28+at+10.02.01+PM.png" width="288" /></a></div>
<br />
<br />
We also need Angular to capture this hashbang path and try to load it via JavaScript instead of static files. In app.module.ts add:<br />
<br />
<pre class="brush:js">import { PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
import { Inject } from '@angular/core';
</pre>
<br />
Then we need to add an if statement to ensure code is only run when within a browser (skipped when Universal static generation runs). Then it can check the hashbang url and if it exists in JavaScript then load the url dynamically.<br />
<br />
<pre class="brush:js">export class AppModule {
constructor(@Inject(PLATFORM_ID) private platformId: Object) {
if (isPlatformBrowser(this.platformId)) {
var hash = location.hash.replace('#!/', '');
if (hash.length > 1) {
history.pushState({}, "entry page", hash);
}
}
}
}
</pre>
<br />
This way you get the full benefits of static hosting, but if the static file does not exist yet, it will fallback to index.html and JavaScript will attempt to render the template using the API.<br />
<br />
You can download the completed example project from <a href="https://github.com/kmturley/universal-starter/tree/s3-hosting-redirects">my fork</a> at:<br />
<br />
<pre class="brush:js">git clone https://github.com/kmturley/universal-starter/tree/s3-hosting-redirects
</pre>
<br />
Hope that helps you get started using Angular Universal and Amazon S3!</div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-81849372151963661772017-06-29T12:14:00.001-07:002017-06-29T12:19:52.292-07:00Continuous Integration with Gitlab, Selenium and Google Cloud SDK<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTTYYReQ6XmceEAVvhJ6aTz11boqCDfz2Na0NypAiE3TWMYBCSPHqyy5Hn7ygp000_mBKwA0sEc4x5TwRniPCvchnxGAUK47MBDmDAVueI-4hfjaRd7AOMmRVfBKcvvfNdqsgEoYUBULw/s1600/Screen+Shot+2017-06-29+at+2.13.04+PM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="334" data-original-width="764" height="86" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTTYYReQ6XmceEAVvhJ6aTz11boqCDfz2Na0NypAiE3TWMYBCSPHqyy5Hn7ygp000_mBKwA0sEc4x5TwRniPCvchnxGAUK47MBDmDAVueI-4hfjaRd7AOMmRVfBKcvvfNdqsgEoYUBULw/s200/Screen+Shot+2017-06-29+at+2.13.04+PM.png" width="200" /></a></div>
As development tools have improved, we have started to see Continuous Integration workflows which allow code to be stored, tested and deployed easily. This post is going to look at one of the most popular open-source platforms for developers: Gitlab.<br />
<br />
<a name='more'></a><br />
<br />
Gitlab combines code storage with, issue tracking, testing and deployment tools. It's designed for the complete development workflow from start to finish. We are going to play with the Gitlab pipelines and setup a complete automated workflow based on branches.<br />
<br />
<br />
1) Setup Gitlab. Either <a href="https://gitlab.com/users/sign_in">sign-in</a> to the free hosted version, or <a href="https://about.gitlab.com/installation/">download and install</a> your own version.<br />
<br />
<br />
2) Create a new Project and download the code repository to your local machine.<br />
<br />
<br />
3) Create a backend codebase. In my example i've decided to create a simple Python/Flask application which renders a single page:<br />
<br />
backend/requirements.txt<br />
<pre class="brush:javascript">Flask==0.12</pre>
<br />
backend/__init__.py<br />
<pre class="brush:javascript">import os, sys
lib_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'lib')
sys.path.insert(0, lib_path)
from .server import app
if __name__ == "__main__":
app.run()</pre>
<br />
backend/server.py<br />
<pre class="brush:javascript">import os
from flask import Flask, render_template, send_from_directory
static_dir = os.path.join(os.path.dirname(__file__), 'static')
template_dir = os.path.join(os.path.dirname(__file__), 'templates')
app = Flask(__name__, static_folder=static_dir, template_folder=template_dir)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/api')
def test():
return 'Hello people!'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3000, debug=True)
</pre>
<div>
<br /></div>
backend/templates/index.html<br />
<pre class="brush:javascript"><p>it works</p></pre>
<br />
You can run the application using the commands:<br />
<br />
<pre class="brush:javascript">pip install -r requirements.txt
python server.py</pre>
<br />
<br />
4) In the root of the project we can now add support for Google AppEngine using:<br />
<br />
app.yml<br />
<pre class="brush:javascript">runtime: python27
threadsafe: true
handlers:
- url: /
script: backend.app
- url: /static
static_dir: backend/static
skip_files:
- ^(.*/)?#.*#$
- ^(.*/)?.*~$
- ^(.*/)?.*\.py[co]$
- ^(.*/)?.*/RCS/.*$
- ^(.*/)?\..*$
- ^(.*/)?.*\_test.(html|js|py)$
- ^(.*/)?.*\.DS_Store$
- ^.*bower_components(/.*)?
- ^.*node_modules(/.*)?
- ^.*jspm_packages(/.*)?
</pre>
<div>
<br /></div>
<div>
<br /></div>
<div>
5) Make sure you have installed the <a href="https://cloud.google.com/sdk/docs/quickstarts">Google Cloud SDK</a>. We can now deploy this manually to AppEngine using the commands:<br />
<br /></div>
<pre class="brush:javascript">gcloud init
gcloud app deploy</pre>
<br />
But we want to automate this process, how?
<br />
<br />
<br />
6) Create a .gitlab-ci.yml file in the root of your project containing the steps to build the backend, and deploy the code:<br />
<br />
.gitlab-ci.yml<br />
<pre class="brush:javascript">build_backend:
image: python
stage: build
script:
- pip install -t backend/lib -r backend/requirements.txt
- export PYTHONPATH=$PWD/backend/lib:$PYTHONPATH
artifacts:
paths:
- backend/
deploy:
image: google/cloud-sdk
stage: deploy
environment:
name: $CI_BUILD_REF_NAME
url: https://$CI_BUILD_REF_SLUG-dot-$GAE_PROJECT.appspot.com
script:
- echo $GAE_KEY > /tmp/gae_key.json
- gcloud config set project $GAE_PROJECT
- gcloud auth activate-service-account --key-file /tmp/gae_key.json
- gcloud --quiet app deploy --version $CI_BUILD_REF_SLUG --no-promote
after_script:
- rm /tmp/gae_key.json
</pre>
<div>
<br /></div>
<div>
This will use the current branch name as the version, so if you commit code to master, your appengine url will be <a href="https://master-dot-projectname.appspot.com/">https://master-dot-projectname.appspot.com</a></div>
<div>
<br />
<br /></div>
<div>
7) Last step before committing your changes to Gitlab (and running the pipeline), you need to set the variables. Go to:</div>
<div>
Gitlab Project > Settings > CI/CD Pipelines > Secret Variables</div>
<div>
<br /></div>
<div>
And fill out the following variables:<br />
<div>
* GAE_KEY: Secret key for a user with the required permissions.</div>
<div>
* GAE_PROJECT: Name of the target AppEngine application.</div>
</div>
<div>
<br /></div>
<div>
You will need to get these from Google Cloud at:<br />
Google Cloud Project > IAM & Admin > Service Accounts > Create Service Acccount</div>
<div>
<br />
<br /></div>
<div>
8) If you push the code to Gitlab, you should now see the steps running one-by-one:</div>
<div>
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZNjpvkQp5YnR3BFWY5yQ9qP2GO__uYDA_mCCExp4ZUUDcoKD7cYpgOendFmN5zFnO2jOxQp3qnScv9t3joLvkZXucGDhGFOM8Kq7ZoRzr21eXgl_JJueiFaxoIEdF_SGeGEY3ljc7fok/s1600/Screen+Shot+2017-06-29+at+2.13.04+PM.png" imageanchor="1"><img border="0" height="175" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZNjpvkQp5YnR3BFWY5yQ9qP2GO__uYDA_mCCExp4ZUUDcoKD7cYpgOendFmN5zFnO2jOxQp3qnScv9t3joLvkZXucGDhGFOM8Kq7ZoRzr21eXgl_JJueiFaxoIEdF_SGeGEY3ljc7fok/s400/Screen+Shot+2017-06-29+at+2.13.04+PM.png" width="400" /></a></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div>
Click on a failed job to see the output of the errors:<br />
<br />
<div style="text-align: left;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQZ3QC1l041ls0ZMOJjNb6tiCIHC40jbFt3gUXtJNvfQLSFpKFezt8sPzy5mXSfEM7-ws4bo6yrVZwER3DveLsNJ2_MdoMhCW3pWv_aX6urolXmIfTjVMxb5Vgvxor4blVeJdX_Dscex8/s1600/Screen+Shot+2017-06-29+at+2.37.25+PM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="145" data-original-width="608" height="76" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQZ3QC1l041ls0ZMOJjNb6tiCIHC40jbFt3gUXtJNvfQLSFpKFezt8sPzy5mXSfEM7-ws4bo6yrVZwER3DveLsNJ2_MdoMhCW3pWv_aX6urolXmIfTjVMxb5Vgvxor4blVeJdX_Dscex8/s320/Screen+Shot+2017-06-29+at+2.37.25+PM.png" width="320" /></a></div>
</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br />
<br />
9) Last step is to add some unit/functional tests. I've decided to go use PyUnit and Selenium tools to make the process easier. First we update our project code:<br />
<br />
backend/requirements.txt<br />
<pre class="brush:javascript">Flask==0.12
chromedriver-installer==0.0.6
selenium==3.4.2
</pre>
<div>
<br /></div>
backend/test_server.py<br />
<pre class="brush:javascript">import unittest
from backend import app
class Test(unittest.TestCase):
def test(self):
result = app.test_client().get('/api')
self.assertEqual(result.data.decode('utf-8'), 'Hello people!')
</pre>
<div>
<br />
qa/browser.py<br />
<pre class="brush:javascript">import os
import unittest
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
DRIVER = os.getenv('DRIVER', 'headless_chrome')
BASE_URL = os.getenv('BASE_URL', 'http://backend:3000')
SELENIUM = os.getenv('SELENIUM', 'http://localhost:4444/wd/hub')
def get_chrome_driver():
desired_capabilities = webdriver.DesiredCapabilities.CHROME
desired_capabilities['loggingPrefs'] = {'browser': 'ALL'}
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument(
"--user-data-dir=/tmp/browserdata/chrome \
--disable-plugins --disable-instant-extended-api")
desired_capabilities.update(chrome_options.to_capabilities())
browser = webdriver.Chrome(
executable_path='chromedriver',
desired_capabilities=desired_capabilities)
# Desktop size
browser.set_window_position(0, 0)
browser.set_window_size(1366, 768)
return browser
def get_headless_chrome():
desired_capabilities = webdriver.DesiredCapabilities.CHROME
desired_capabilities['loggingPrefs'] = {'browser': 'ALL'}
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument(
"--user-data-dir=/tmp/browserdata/chrome \
--disable-plugins --disable-instant-extended-api \
--headless")
desired_capabilities.update(chrome_options.to_capabilities())
browser = webdriver.Remote(
command_executor=SELENIUM,
desired_capabilities=desired_capabilities)
# Desktop size
browser.set_window_position(0, 0)
browser.set_window_size(1366, 768)
return browser
DRIVERS = {
'chrome': get_chrome_driver,
'headless_chrome': get_headless_chrome
}
def get_browser_driver():
return DRIVERS.get(DRIVER)()</pre>
<br />
qa/test_example.py<br />
<pre class="brush:javascript">import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from browser import get_browser_driver, BASE_URL
class ExampleTestClass(unittest.TestCase):
def setUp(self):
print("BASE_URL", BASE_URL)
self.driver = get_browser_driver()
def tearDown(self):
self.driver.quit()
def test_page_title(self):
self.driver.get(BASE_URL)
print("driver.title", self.driver.title)
self.assertIn("Gitlab AppEngine CI", self.driver.title)
elem = self.driver.find_element(By.NAME, "search")
elem.send_keys("Selenium")
# elem.send_keys(Keys.RETURN)
# assert "No results found." not in driver.page_source
def test_javascript_text(self):
self.driver.get(BASE_URL)
wait = WebDriverWait(self.driver, 10)
wait.until(EC.visibility_of_element_located(
(By.CSS_SELECTOR, 'div#output')))
elem = self.driver.find_element(By.ID, 'output')
self.assertIn("JavaScript + gulp too!", elem.text)
if __name__ == "__main__":
unittest.main()
</pre>
<div>
<br /></div>
<br /></div>
You should be able to run the tests manually using the commands:<br />
<br />
<pre class="brush:javascript">python -m unittest discover -s backend
python -m unittest discover -s qa</pre>
<br />
<br />
10) Let's automate the unit/functional tests by adding the following lines to the .gitlab-ci.yml file:<br />
<br />
.gitlab-ci.yml<br />
<pre class="brush:javascript">test_unit:
image: python
stage: test
script:
- export PYTHONPATH=$PWD/backend/lib:$PYTHONPATH
- python -m unittest discover -s backend
test_functional:
image: python
stage: test
services:
- selenium/standalone-chrome
script:
- export PYTHONPATH=$PWD/backend/lib:$PYTHONPATH
- SELENIUM="http://selenium__standalone-chrome:4444/wd/hub" BASE_URL="https://$CI_BUILD_REF_SLUG-dot-$GAE_PROJECT.appspot.com" DRIVER="headless_chrome" python -m unittest discover -s qa</pre>
<br />
Hope that helps you get set up!<br />
<br />
View the full working code here:<br />
<a href="https://gitlab.com/kim3/gitlab-appengine-ci">https://gitlab.com/kim3/gitlab-appengine-ci</a></div>
</div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-65538742608933534062017-01-27T14:27:00.001-08:002017-01-27T14:27:25.599-08:00Python, Docker and Amazon Elastic Beanstalk<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjT1247JYCQwYKf-7olat98z1I4e_50o6zAUuvQaoo5op9_7OWP11V9Ufvnv4k47I9w2ZKvPcvGS_1qYw-hluca_cXa4S-vgrQLcS1gO409sVVb7gaEwgKfLHgVsUGD-_89hYDufc7Kdrg/s1600/python-docker-amazon.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="112" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjT1247JYCQwYKf-7olat98z1I4e_50o6zAUuvQaoo5op9_7OWP11V9Ufvnv4k47I9w2ZKvPcvGS_1qYw-hluca_cXa4S-vgrQLcS1gO409sVVb7gaEwgKfLHgVsUGD-_89hYDufc7Kdrg/s200/python-docker-amazon.png" width="200" /></a></div>
When delving into the world of infrastructure there are many choices of cloud platform, ranging from completely custom setups, to off-the-shelf solutions such as Google AppEngine.<br />
<br />
Today i'm looking at how off-the-shelf infrastructure (Amazon Elastic Beanstalk in particular) can speed up DevOps and deployments, and reduce the overhead on maintenance.<br />
<br />
<a name='more'></a>For my particular scenario I wanted to replicate a common technology stack I like to use:<br />
<div style="text-align: left;">
</div>
<ul style="text-align: left;">
<li>Backend: Python + Django</li>
<li>Frontend: HTML + JavaScript</li>
<li>Task runners: NPM + Gulp</li>
<li>Infrastructure: Docker + AWS</li>
</ul>
<div>
There are many ways to implement this particular stack, but Amazon Elastic Beanstalk promises to give you a one-click deploy solution. So let's give it a go!</div>
<div>
<br /></div>
First step was to install the <a href="http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/eb-cli3-install.html">Amazon Elastic Beanstalk Client</a> using the command:
<br />
<div>
<br /></div>
<pre class="brush:js">brew install awsebcli</pre>
<br />
If you already have <a href="https://www.djangoproject.com/">Django</a> installed, you can auto-generate a project using the command:<br />
<br />
<pre class="brush:js">django-admin startproject mysite</pre>
<br />
You are able to test your django project locally with python runserver:<br />
<br />
<pre class="brush:js">cd mysite
python manage.py runserver 0.0.0.0:8080</pre>
<br />
Next we need to add a Docker file at /mysite/Dockerfile. This will tell docker to extend from a pre-configured Amazon base Docker image. So add the following lines to the file:<br />
<br />
<pre class="brush:js"># Use the AWS Elastic Beanstalk Python 3.4 image
FROM amazon/aws-eb-python:3.4.2-onbuild-3.5.1
# Expose port
EXPOSE 8080
</pre>
<br />
If you prefer you can write your own custom Docker images, there are <a href="https://github.com/search?o=desc&q=eb+django&s=stars&type=Repositories&utf8=%E2%9C%93">plenty of examples on github</a>. We can now initiate our amazon project using the command (follow the steps to complete):<br />
<br />
<pre class="brush:js">eb init</pre>
<br />
Your mysite/ folder should now contain an .elasticbeanstalk sub-folder containing the settings you just entered. This will be used for building and deploying to amazon. Before you run the Docker container you should ensure you have a .dockerignore file containing:<br />
<br />
<pre class="brush:js">.elasticbeanstalk/*
.git
.gitignore
.pyc</pre>
<br />
Without this, Docker will copy ALL files and could cause build/performance issues. You can now run the docker container using the handy Elastic Beanstalk Client shortcut:<br />
<br />
<pre class="brush:js">eb local run</pre>
<br />
Or if you'd prefer, you can use the docker commands directly:<br />
<br />
<pre class="brush:js">docker build -t python-docker-amazon .
docker run -it -p 3000:8080 python-docker-amazon</pre>
<br />
To deploy your application you can use the Elastic Beanstalk Client commands:<br />
<br />
<pre class="brush:js">eb start
eb deploy</pre>
<br />
A very simple intro into using Elastic Beanstalk!<br />
<br />
You can view the completed project on github here:<br />
<a href="https://github.com/kmturley/python-docker-amazon">https://github.com/kmturley/python-docker-amazon</a><br />
<br />
I have also created a Flask branch to show how it works with multiple Python frameworks:<br />
<a href="https://github.com/kmturley/python-docker-amazon/tree/flask">https://github.com/kmturley/python-docker-amazon/tree/flask</a></div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-71193618823935452902016-12-02T15:08:00.001-08:002017-01-27T12:49:50.323-08:00Universal decimal calendar and time system<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRgEUwdNEOxljoop83gxqPpl5HPe-M90n2I3iCIzQe6GiYS6iMUMpw_2qpvCcDlvmNKijmM6Z7-Co5cUhYN-kTZE_9gOzLVzVuYSTUXmT2O3on3pe2iMEZHUUPoZFLzUnJGG-UYCMFWWo/s1600/decimal-time.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="112" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRgEUwdNEOxljoop83gxqPpl5HPe-M90n2I3iCIzQe6GiYS6iMUMpw_2qpvCcDlvmNKijmM6Z7-Co5cUhYN-kTZE_9gOzLVzVuYSTUXmT2O3on3pe2iMEZHUUPoZFLzUnJGG-UYCMFWWo/s200/decimal-time.jpg" width="200" /></a></div>
Our calendar and time system has been around for a very long time, it works well but there are still many painpoints especially timezones, converting minutes into hours and more.<br />
<br />
Here we are going to look at creating a new date/time system which is decimalised and can be used worldwide.<br />
<br />
<a name='more'></a>A long time ago <a href="https://en.wikipedia.org/wiki/French_Republican_Calendar">French Republican Calendar system</a> was an attempy to decimalise time, along with currency and metrication. It was used between 1793 and 1805 but ultimately failed because of adoption and confusion between the multiple calendar systems. However it was a great idea, and I would like to continue/improve it.<br />
<div>
<br /></div>
<div>
<br />
<div>
Some things can't change, for example:</div>
<div>
<ul style="text-align: left;">
<li>the length of a year (spring/summer/autumn/winter)</li>
<li>the length of a day (daylight/nighttime cycle)</li>
</ul>
<div>
But some things are flexible:</div>
</div>
<div>
<ul style="text-align: left;">
<li>the length of a month</li>
<li>the length of an hour</li>
<li>the length of a minute</li>
<li>the length of a second</li>
</ul>
<div>
Some of the changes i'd like to make:</div>
</div>
<div>
<div>
<ul style="text-align: left;">
<li>12 months > 10 months</li>
<li>52 weeks > 36.5 weeks</li>
<li>28/31 days per month > 36/37 days per month</li>
<li>24 hours > 20 hours</li>
<li>60 minutes > 100 minutes</li>
<li>60 seconds > 100 seconds</li>
</ul>
</div>
</div>
<div>
How can we achieve this? I've created a prototype of a clock using JavaScript. Check out my example below:<br />
<br /></div>
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="300" src="//jsfiddle.net/kmturley/7mrwc3x3/11/embedded/result,js,html/" width="100%"></iframe>
<br />
<div>
<br />
If you would like to contribute to the project you can view the source code on github here:<br />
<a href="https://github.com/kmturley/decimal-time">https://github.com/kmturley/decimal-time</a><br />
<br />
You can view the same demo here:<br />
<a href="https://kmturley.github.io/decimal-time/">https://kmturley.github.io/decimal-time/</a></div>
</div>
</div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-66959968710769566522016-07-26T11:52:00.004-07:002016-07-26T11:52:50.212-07:00Detect speech using the Web Speech Recognition API<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibmHAjKZrmXvuOj_irf2yO57qkcSvQFi0VL9TSnvlWDXwfXrxSaJgNRt10bV2WZakXUX1bOwbF7cFcHPO77Fpg14hXNOyOdvArwCgPguv3sMqH1KZSfxIsPbRwJ0xh6Zi7LLxuTMBuOK0/s1600/6505-94022a.gif" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="166" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibmHAjKZrmXvuOj_irf2yO57qkcSvQFi0VL9TSnvlWDXwfXrxSaJgNRt10bV2WZakXUX1bOwbF7cFcHPO77Fpg14hXNOyOdvArwCgPguv3sMqH1KZSfxIsPbRwJ0xh6Zi7LLxuTMBuOK0/s200/6505-94022a.gif" width="200" /></a></div>
<br />
As the web matures, we can use interfaces and sensors with improved cross browser/device compatibility. However normally we are given access to the raw data and expected to do our own processing. This has made detecting speech in audio data very intensive, and inaccessible for lower end devices.<br />
<br />
The formalising of a Web Speech API opens up a realm of possibilities because it moves the processing into the native browser code and out of javascript. We can now access speech input data much easier and efficiently.<br />
<br />
<a name='more'></a><br />
Lets create a simple example to show how you can detect speech and then trigger an action based on the text. This example will work in Chrome (Desktop, Android) and Firefox.<br />
<br />
First we need to create a Speech recognition object:<br />
<br />
<pre class="brush:js">var SpeechRecognition = SpeechRecognition || webkitSpeechRecognition,
recognition = new SpeechRecognition(),
</pre>
<br />
Next we can set some options, in this case we want continous speech processing and returning incomplete results:<br />
<br />
<pre class="brush:js">recognition.continuous = true;
recognition.interimResults = true;
</pre>
<br />
After this we need to create the functions to receive the data:<br />
<br />
<pre class="brush:js">recognition.onstart = function(e) {
console.log('onstart', e);
};
recognition.onend = function(e) {
console.log('onend', e);
};
recognition.onresult = function(e) {
console.log('onresult', e);
};
recognition.onspeechend = function(e) {
console.log('onspeechend', e);
};
recognition.onerror = function(e) {
console.log('onerror', e);
};
</pre>
<br />
We can now view console logs of our data by starting recognition playback:<br />
<br />
<pre class="brush:js">recognition.start();
</pre>
<br />
If we want to output our text into the page we can update our onresult function:<br />
<br />
<pre class="brush:js">recognition.onresult = function(e) {
console.log('onresult', e);
var i = 0,
html = '';
for (i = 0; i < e.results.length; i += 1) {
html += e.results[i][0].transcript;
}
document.getElementById('output').innerHTML = html;
};
</pre>
<br />
See a full working version here:<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="300" src="//jsfiddle.net/kmturley/c0Lnppcz/4/embedded/" width="100%"></iframe>
<a href="https://jsfiddle.net/kmturley/c0Lnppcz/4/">https://jsfiddle.net/kmturley/c0Lnppcz/4/</a><br />
<br /></div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-44722673207437158252016-02-10T15:24:00.001-08:002016-02-10T15:42:24.112-08:00Use ES6 JavaScript today using System.JS, Babel, JSPM and Gulp<div dir="ltr" style="text-align: left;" trbidi="on">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgM47SIkzEocP_4yKvfqdJLsBhC8dBO7jCXd5a3oDbt-_byPSnph9XGO1fN_zB5wwOAuG2ISBS7GVpW3-0yaWkWjNp1arxgQXlLGfQXhueu-TSe2T6ses2DWh-a5WR7mbOsqk-CttNyQFg/s1600/jspm-babel-gulp.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="136" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgM47SIkzEocP_4yKvfqdJLsBhC8dBO7jCXd5a3oDbt-_byPSnph9XGO1fN_zB5wwOAuG2ISBS7GVpW3-0yaWkWjNp1arxgQXlLGfQXhueu-TSe2T6ses2DWh-a5WR7mbOsqk-CttNyQFg/s320/jspm-babel-gulp.jpg" width="320" /></a>Developers are always looking forward to future JavaScript features, including structured classes shorthand coding and helpers. However many of these features are not standardised and/or <a href="https://kangax.github.io/compat-table/es6/">compatible with all modern browsers</a>, and the ones that are agreed are not supported by legacy browsers.<br />
<br />
There are many tools which allow you as a developer to write using future features, and the tools will convert your code to support other browsers. Today we are going to look at how they work and an example workflow which you can use now on live projects.<br />
<br />
<a name='more'></a><br />
First off lets create a JavaScript file with some ES6 syntax code. Lets use the new <a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/default_parameters">default parameters syntax</a> (which is not yet supported by many browsers) called src/all.js<br />
<br />
<pre class="brush:js">import multiply from './multiply';
document.getElementById('list').innerHTML = multiply(5);
</pre>
<br />
And create a file src/multiply.js<br />
<pre class="brush:js">var multiply = (a, b = 2) => {
return a * b;
}
export default multiply;
</pre>
<br />
And we will also create a simple html page loading the javascript src/index.html<br />
<br />
<pre class="brush:html"><!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>ES6 Modules</title>
<meta name="description" content="" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
</head>
<body>
<div id="list"></div>
<script src="all.js"></script>
</body>
</html>
</pre>
<br />
When this is loaded within most browsers it will error "Unexpected token import", as the browser does not understand the import syntax yet. So how can we add support for ES6?<br />
<br />
The answer is to add a JavaScript compiler called <a href="https://babeljs.io/">Babel.js</a> which will transform your ES6 code into ES5. There are different ways to run Babel on your code. You could use a command line on your script, or the online compiler, but we are going to use a module loader which will compile the files dynamically inside your browser. Much easier!<br />
<br />
Firstly we will add the <a href="https://github.com/systemjs/systemjs">System.js</a> module loader, which will load your all.js file and then run <a href="https://babeljs.io/">Babel.js</a> compiler on it, and run the result for you.<br />
<br />
1) Ensure you have started a new npm project and have jspm installed:<br />
<pre class="brush:js">npm install jspm --save-dev</pre>
<br />
2) Next you need to initialise jspm in the folder root:<br />
<pre class="brush:js">jspm init</pre>
<br />
3) Fill out the questions it asks using the options below:
<br />
<pre class="brush:js">Package.json file does not exist, create it? [yes]: yes
Would you like jspm to prefix the jspm package.json properties under jspm? [yes]: yes
Enter server baseURL (public folder path) [./]: ../
Enter jspm packages folder [.\jspm_packages]:
Enter config file path [.\config.js]:
Configuration file config.js doesn't exist, create it? [yes]: yes
Enter client baseURL (public folder URL) [/]: ../
Do you wish to use a transpiler? [yes]: yes
Which ES6 transpiler would you like to use, Babel, TypeScript or Traceur? [babel]: babel</pre>
<br />
4) JSPM has automatically installed the System.js module loader for you. So update your index.html to load it into the page:<br />
<pre class="brush:html"><!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>ES6 Modules</title>
<meta name="description" content="" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
</head>
<body>
<div id="list"></div>
<script src="../jspm_packages/system.js"></script>
<script src="../config.js"></script>
<script>
System.import('./all.js');
</script>
</body>
</html>
</pre>
<div>
<br />
Now when you run this page in your browser you should see the Number outputted! Great stuff, so what next? You can use this for development, but every time you change a file you need to reload the page and all of the System.js polyfill code which takes time. Lets add an automated task to watch our javascript files and reload them in the page.<br />
<br />
1) Add gulp-connect package using the command:<br />
<pre class="brush:js">npm install gulp --save-dev
npm install gulp-connect --save-dev
</pre>
<br />
2) Create a gulpfile.js containing the following code:<br />
<pre class="brush:js">var connect = require('gulp-connect'),
gulp = require('gulp');
gulp.task('connect', function () {
return connect.server({
root: '',
livereload: true,
port: 8181
});
});
gulp.task('watch', function() {
gulp.watch(['src/**/*.js'], function(event) {
gulp.src(event.path)
.pipe(connect.reload());
});
});
gulp.task('default', ['connect', 'watch']);</pre>
<br />
3) Now run the command in your terminal:<br />
<pre class="brush:js">gulp</pre>
<br />
4) Go to http://localhost:8181/ in your browser and view the Network requests to see a livereload script being loaded. Try changing your js file and saving it to see the automatic reload!<br />
<br />
To create the ES5 version for deployment you will need to:<br />
<br />
1) First install the gulp-jspm library to ease use with gulp streams:<br />
<pre class="brush:js">npm install gulp-jspm --save-dev</pre>
<br />
2) Next include it in your gulp file along with your new task:<br />
<pre class="brush:js">
var jspm = require('gulp-jspm');
gulp.task('compile', function () {
return gulp.src('src/all.js')
.pipe(jspm({selfExecutingBundle: true}))
.pipe(gulp.dest('src'));
});</pre>
3) Run the gulp task and view the new all.bundle.js file created:<br />
<pre class="brush:js">gulp compile</pre>
<br />
You can view a more developed version of this structure here including other tools such as SASS and modular components:<br />
<a href="https://github.com/kmturley/es6-modules">https://github.com/kmturley/es6-modules</a></div>
</div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-62228461993601990912015-12-23T08:15:00.005-08:002015-12-23T08:18:56.592-08:00Trimming videos using AngularJS<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQyMGpSQ3UPDhhk81vTDo0rHPZGXjgQg581rbrAJSoJlUAimVNXhg0i9XqXuScahBVWaCaZl8-PxIVb8NGJSMc2NcZPvno4LQlMhrUfVQ1fZhb8VitpqOygbx0JpCp112e6xQxuTThFzk/s1600/trim-video.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="135" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQyMGpSQ3UPDhhk81vTDo0rHPZGXjgQg581rbrAJSoJlUAimVNXhg0i9XqXuScahBVWaCaZl8-PxIVb8NGJSMc2NcZPvno4LQlMhrUfVQ1fZhb8VitpqOygbx0JpCp112e6xQxuTThFzk/s200/trim-video.jpg" width="200" /></a></div>
Playing video within the browser has become a fairly painless experience using the html5 video player. But there are many challenges as you add complexity to the controls. One of these is how you could set in and out points.<br />
<br />
Here we will look at a way to trim videos using the <a href="https://github.com/PopSugar/angular-slider" target="_blank">angular-slider</a> library and AngularJS.<br />
<br />
<a name='more'></a>First off we need to start an Angular app with a directive to handle our custom video player:<br />
<br />
<pre class="brush:html"><div class="app" ng-app="app">
<player class="player"></player>
</div>
</pre>
<br />
Next we will create the AngularJS app class and the directive to hold our custom player code:<br />
<br />
<pre class="brush:js">angular.module('app', [])
.directive('player', function () {
'use strict';
return {
restrict: 'E',
template: '<div></div>'
link: function (scope, element, attrs) {
console.log('link');
}
};
});
</pre>
<br />
Next we want to update the template to container the video player and slider html:<br />
<br />
<pre class="brush:js">template: '<div class="wide"><video autoplay controls><source ng-src="http://mirror.cessen.com/blender.org/peach/trailer/trailer_iphone.m4v" type="video/mp4" /></video></div>' +
'<slider floor="0" ceiling="100" step="1" ng-model="currentPercent" change="onSliderChange()"></slider>' +
'<slider floor="0" ceiling="{{ duration }}" step="0.1" precision="10" ng-model-low="start" ng-model-high="end" change="onTrimChange()"></slider>' +
'<p>start = {{ start }}</p><p>current = {{ current }}</p><p>end = {{ end }}</p>'
</pre>
<br />
To render the custom sliders we need to include the ui-slider code from:<br />
<a href="https://github.com/PopSugar/angular-slider">https://github.com/PopSugar/angular-slider</a><br />
<br />
You will need to include the library as a dependancy to the app class with:<br />
<br />
<pre class="brush:js">angular.module('app', ['ui-slider'])</pre>
<br />
In addition to this we will also add css to handle responsive widescreen video aspect ratio:<br />
<br />
<pre class="brush:css">.player .wide {
position: relative;
padding-bottom: 56.25%;
height: 0;
overflow: hidden;
}
.player .wide video {
background-color: #000;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</pre>
<br />
Lastly we will add some custom directive functions to calculate the slider events. So as sliders are moved, the correct values are calculated. This code is put within the directive link function:<br />
<br />
<pre class="brush:js">link: function (scope, element, attrs) {
var interval = 0,
video = element.find('video');
video.on('loadeddata', function (e) {
scope.$apply(function () {
scope.start = 0;
scope.end = e.target.duration;
scope.current = scope.start;
scope.currentPercent = scope.start;
scope.duration = scope.end;
});
});
video.on('timeupdate', function (e) {
scope.$apply(function () {
scope.current = (e.target.currentTime - scope.start);
scope.currentPercent = (scope.current / (scope.end - scope.start)) * 100;
if (e.target.currentTime < scope.start) {
e.target.currentTime = scope.start;
}
if (e.target.currentTime > scope.end) {
if (video[0].paused === false) {
e.target.pause();
} else {
e.target.currentTime = scope.start;
}
}
});
});
scope.onTrimChange = function () {
video[0].pause();
};
scope.onSliderChange = function () {
video[0].pause();
if (interval) {
window.clearInterval(interval);
}
interval = window.setTimeout(function () {
video[0].currentTime = scope.start + ((scope.currentPercent / 100 ) * (scope.end - scope.start));
}, 300);
};
scope.$watch('start', function (num) {
if (num !== undefined) {
video[0].currentTime = num;
}
});
scope.$watch('end', function (num) {
if (num !== undefined) {
video[0].currentTime = num;
}
});
},
</pre>
<br />
Now you should have a working range slider, which will update the video start and end times. I've created a working example here:<br />
<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="650" src="//jsfiddle.net/kmturley/odprs5rp/5/embedded/result,html,css,js" width="100%"></iframe>
<br />
<br />
<a href="https://jsfiddle.net/kmturley/odprs5rp/5/">https://jsfiddle.net/kmturley/odprs5rp/5/</a></div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-78873347678195915592015-11-25T08:06:00.001-08:002017-03-15T09:04:56.587-07:00A/B Tests using Google Analytics, Content Experiments and JavaScript<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmwJqOwiVBE7wAlrNsCnpZ6s2f5iXM39l9CWWUKuyusTPgkRzDBCY00SJ6AtrUIZkx9w0f8yVMVH_XuFNAy-qYza0gmSZsQYLIZFqiDwTFTGbe3zAtrEm0vdO7bSzQ1ORl724nm7hHlyM/s1600/ab-test2.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmwJqOwiVBE7wAlrNsCnpZ6s2f5iXM39l9CWWUKuyusTPgkRzDBCY00SJ6AtrUIZkx9w0f8yVMVH_XuFNAy-qYza0gmSZsQYLIZFqiDwTFTGbe3zAtrEm0vdO7bSzQ1ORl724nm7hHlyM/s200/ab-test2.jpg" width="200" /></a></div>
When creating templates on high-end websites, it's useful to know how well the template was performing before and how that performance changed after updating. This can be achieved using Google Analytics (bounce rate, session duration and goals).<br />
<br />
But what happens when the performance drops drastically and you want to go back to the original? How can you do this without risking losing sales or traffic? A/B tests allow you to test multiple variations of the template and measure which performs better.<br />
<br />
Here we will look how to do it using free Google tools.<br />
<a name='more'></a><br />
First off you will need a Google Analytics account. Go to <a href="https://www.google.com/analytics/">https://www.google.com/analytics/</a> and create an account. Then create a GA id and embed code ready for later.<br />
<br />
Next within Google Analytics go into Content Experiments and create a new experiment, you will need to enter the urls for each variation. Just use your base url and add url parameters for the other variations e.g:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8UjmQsX2oAfzTTOvyI6LSrh77nfod1ftdj5Bz-xStBLYX1Cdmi9osiLqCyBPET_PWeCYKmvBs3E4Qu9V0AIDCml57d2MuwY5_tbjyXjxpqCq0JCUbtcUXzJAyPvygK2z1XVfsvHrBS-I/s1600/ab-test3.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="464" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8UjmQsX2oAfzTTOvyI6LSrh77nfod1ftdj5Bz-xStBLYX1Cdmi9osiLqCyBPET_PWeCYKmvBs3E4Qu9V0AIDCml57d2MuwY5_tbjyXjxpqCq0JCUbtcUXzJAyPvygK2z1XVfsvHrBS-I/s640/ab-test3.jpg" width="640" /></a></div>
<br />
<br />
After creating the experiment you will get a Content Experiment ID, we can embed this into the top of our page. <b>Important: we want to load the Content Experiment code first before anything else for speed of switching the layout!</b><br />
<br />
<pre class="brush:html"><script src="//www.google-analytics.com/cx/api.js?experiment=XXXXXXX"></script>
</pre>
<br />
Next we want to setup the url params to override variations. This is to allow us to bypass the experiment and view a variation (for development and testing purposes). Add the following code directly after the api.js script tag:<br />
<br />
<pre class="brush:js"> var param = window.location.search.split('var='),
id = param[1] ? param[1] : cxApi.chooseVariation(),
html = document.getElementsByTagName('html')[0];
html.className = 'var' + id;
</pre>
<br />
This code first splits the url params, and if there is a variation number, it applies a className to the root <html> element like this:<br />
<br />
<pre class="brush:html"><html class="var0">
</pre>
<br />
If the url param does not exist, it will run the Content Experiment method cxApi.chooseVariation() which will choose a random variation based on it's own experiment logic. By appending the variation class to the html element, we can run this script in the head of the document = fastest time. This prevents a flash of switching template layouts.<br />
<br />
Finally then embed your Google Analytics code below the others:<br />
<br />
<pre class="brush:js"><script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXXXXXX-1', 'auto');
ga('send', 'pageview');
</script>
</pre>
<div>
<br /></div>
<div>
This will send a PageView tracking the variation number automatically. You can check this by looking at the Network request for the collect <b>xvar</b> param:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizhF-730Q7emrK525fmB0pyKih-M_QeaS_W3dRnsd-8oDFkAzPV2xyAYYjT_dXQYYTsqR4GtTNVhuWG6RqL0vP8ttQFG_cD50otvjOcwMDZV3La8HaukAAkB63oi5c8LPJuWqrKGVA8b0/s1600/ab-test.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="396" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizhF-730Q7emrK525fmB0pyKih-M_QeaS_W3dRnsd-8oDFkAzPV2xyAYYjT_dXQYYTsqR4GtTNVhuWG6RqL0vP8ttQFG_cD50otvjOcwMDZV3La8HaukAAkB63oi5c8LPJuWqrKGVA8b0/s640/ab-test.jpg" width="640" /></a></div>
<br />
<br /></div>
<div>
Note that this xvar param will not be sent if you bypass the Content Experiment method cxApi.chooseVariation(). Which is any time when using the ?var=1 params for development!<br />
<br />
Here is a JSFiddle showing the code running:<br />
<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="500" src="//jsfiddle.net/z6kyja4z/10/embedded/result,html,css,js" width="100%"></iframe>
<br />
Hope this helps you get set up and running with A/B tests using Google Tools!</div>
</div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-61248524244539605902015-09-23T15:13:00.002-07:002015-09-23T15:13:22.448-07:00Creating a drag drop upload area<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxoMQ3aeeFGw3RZOD66-675Om8uWgdqfTp9b6R4mClhdgCTsVgio2Gi1Ukvbzp8sB_LawVzMSVuaF6YZew34wi7ipqp4-twyFONarnOwVqsG_AVCGH01kIa06eXqAla3YMxF0HmtQSss8/s1600/drag-drop.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="161" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxoMQ3aeeFGw3RZOD66-675Om8uWgdqfTp9b6R4mClhdgCTsVgio2Gi1Ukvbzp8sB_LawVzMSVuaF6YZew34wi7ipqp4-twyFONarnOwVqsG_AVCGH01kIa06eXqAla3YMxF0HmtQSss8/s200/drag-drop.jpg" width="200" /></a></div>
Drag and drop functionality has traditionally been a pain for developers to support. But with advances in browser compatibility it is really easy to support.<br />
<br />
Here we will look at a quick way to implement drag/drop uploads without any external libraries/code.<br />
<br />
<a name='more'></a>First off we need an area in which the user can drop files. This could be the entire page, or just a target area depending on your site. For this example we will use a div containing a file input:<br />
<br />
<pre class="brush:html"><div class="area">
<input type="file" id="upload" />
</div>
</pre>
<br />
Now we want to style up the area to look like a dropzone by adding a dashed border and upload icon:<br />
<br />
<pre class="brush:css">.area {
width: 100%;
height: 100%;
position: absolute;
border: 4px dashed #000;
background-image: url("http://kmtlondon.com/img/upload.png");
background-position: center;
background-repeat: no-repeat;
background-size: 64px 64px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
filter: alpha(opacity=50);
-khtml-opacity: 0.5;
-moz-opacity: 0.5;
opacity: 0.5;
text-align: center;
}
.area:hover,
.area.dragging,
.area.uploading {
filter: alpha(opacity=100);
-khtml-opacity: 1;
-moz-opacity: 1;
opacity: 1;
}
</pre>
<br />
Next we need to use the form input for uploads, but hide the buttons so we can use our custom icon:<br />
<br />
<pre class="brush:css">.area input {
width: 400%;
height: 100%;
margin-left: -300%;
border: none;
cursor: pointer;
}
.area input:focus {
outline: none;
}
</pre>
<br />
Now some vanilla javascript to bring the form input to life:<br />
<br />
<pre class="brush:js">var upload = document.getElementById('upload');
function onFile() {
var me = this,
file = upload.files[0],
name = file.name.replace(/\.[^/.]+$/, '');
console.log('upload code goes here', file, name);
}
upload.addEventListener('dragenter', function (e) {
upload.parentNode.className = 'area dragging';
}, false);
upload.addEventListener('dragleave', function (e) {
upload.parentNode.className = 'area';
}, false);
upload.addEventListener('dragdrop', function (e) {
onFile();
}, false);
upload.addEventListener('change', function (e) {
onFile();
}, false);
</pre>
<br />
If you would like to add file size/type checking, you can use the following code inside your upload function:<br />
<br />
<pre class="brush:js">if (file.type === 'audio/mp3' || file.type === 'audio/mpeg') {
if (file.size < (3000 * 1024)) {
upload.parentNode.className = 'area uploading';
} else {
window.alert('File size is too large, please ensure you are uploading a file of less than 3MB');
}
} else {
window.alert('File type ' + file.type + ' not supported');
}
</pre>
<br />
Here is an example with the code running:<br />
<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="400" src="//jsfiddle.net/kmturley/xf4vLyew/embedded/result,js,css,html" width="100%"></iframe>
<a href="http://jsfiddle.net/kmturley/xf4vLyew/">http://jsfiddle.net/kmturley/xf4vLyew/</a></div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-86349767668702980692015-07-23T15:00:00.002-07:002015-07-23T15:01:22.585-07:00Block based WYSIWYG editor using Angular<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWB-HyjwNsT7qrjzlC181egFgljsdP3qaJEnuKrDKtMg7hkwzHkzW3EbdUSxfYEEKWhRvM__QpB4yF6GrSZP5MlivxoWSpieu1j4G7pTOzFKjnu9SUy_bdMxugmgtfmu4jrNX9jMnJi3s/s1600/angular-sir-trevor.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="195" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWB-HyjwNsT7qrjzlC181egFgljsdP3qaJEnuKrDKtMg7hkwzHkzW3EbdUSxfYEEKWhRvM__QpB4yF6GrSZP5MlivxoWSpieu1j4G7pTOzFKjnu9SUy_bdMxugmgtfmu4jrNX9jMnJi3s/s200/angular-sir-trevor.jpg" width="200" /></a></div>
If you haven't already seen <a href="http://madebymany.github.io/sir-trevor-js/" target="_blank">Sir Trevor JS</a>, then you must take a look now! It allows the user to insert blocks of different types, with a really nice interface.<br />
<br />
There are many benefits to this approach in a responsive site, as the content types are separated using json and can be outputted in rows. I have used <a href="http://madebymany.github.io/sir-trevor-js/" target="_blank">Sir Trevor JS</a> along with <a href="https://wagtail.io/features/streamfield/" target="_blank">Wagtail Streamfield</a>, which does a similar thing.<br />
<br />
But what if we want the same experience on an Angular project? well unfortunately the only version available is a wrapper around the main version, without proper Angular integration. I decided to write my own which could support the integration better. Here is how I did it.<br />
<br />
<a name='more'></a><br />
First off we need to include angular.js in the page and add the ng-app to the html tag. Now we can create a controller for the editor with some default data:<br />
<br />
<pre class="brush:js"> angular.module('app', [])
.controller('editor', ['$scope', '$compile', '$templateCache', function ($scope, $compile, $templateCache) {
'use strict';
$scope.blocks = [
{ type: 'text', content: '<h1>Angular Sir Trevor</h1><p>AngularJS block-based editor inspired by Sir Trevor JS</p>' },
{ type: 'image', content: 'http://massimages.mobi/wp-content/uploads/forest-wallpaper-widescreen-hd-photograph-6.jpg' }
];
}])
</pre>
<br />
Now we have some data we need assign the controller and add the loop which will output the blocks in the html page:<br />
<br />
<pre class="brush: html"><div class="editor" ng-controller="editor">
<div block class="block {{ block.type }}" ng-repeat="block in blocks" ng-class="{ 'editing': block.edit }" ng-click="block.edit=true"></div>
</div>
</pre>
<br />
Note that we also included a directive attribute called 'block'. This is going to be a dynamic directive which changes the block type based on the data. Lets add the directive and a function to the controller to handle it:<br />
<br />
<pre class="brush:js"> // directive
.directive('block', ['$window', '$compile', '$templateCache', function ($window, $compile, $templateCache) {
'use strict';
return {
restrict: 'A',
link: function (scope, element, attr, ctrl) {
scope.updateType(element, scope);
}
};
}])
// add to the editor controller
$scope.updateType = function (element, scope) {
if (scope.block.type === 'text') {
element.removeAttr('tabindex', '0');
element.html($templateCache.get(scope.block.type));
} else {
element.attr('tabindex', '0');
element.html($templateCache.get(scope.block.type) + $templateCache.get('overlay'));
}
$compile(element.contents())(scope);
};
</pre>
<br />
In this updateType function we are finding the template based on the block type. But we are missing templates for the block types. Lets add those to the html:<br />
<br />
<pre class="brush: html"><script type="text/ng-template" id="overlay">
<div class="overlay">
<div class="controls">
<button ng-click="moveUp(blocks, block)">▲</button>
<button ng-click="remove(blocks, block)">X</button>
<button ng-click="moveDown(blocks, block)">▼</button>
</div>
<p>UPLOAD FILE</p>
<div class="area"><input upload type="file" /></div>
<p>OR MODIFY EXISTING URL</p>
<input paste type="text" ng-model="block.content" />
</div>
</script>
<script type="text/ng-template" id="text">
<div text contenteditable="true">{{ block.content }}</div>
<div class="controls">
<button ng-click="moveUp(blocks, block)">▲</button>
<button ng-click="remove(blocks, block)">X</button>
<button ng-click="moveDown(blocks, block)">▼</button>
</div>
</script>
<script type="text/ng-template" id="image">
<img image ng-src="{{ block.content }}" alt="" />
</script>
<script type="text/ng-template" id="video">
<div class="wide"><iframe video content="{{ block.content }}" frameborder="0" allowfullscreen></iframe></div>
</script>
</pre>
<br />
Now the blocks are showing, we need to add the functionality to the controls to move blocks around, add and remove blocks. add these functions to your controller:<br />
<br />
<pre class="brush:js"> $scope.add = function (array, element) {
array.push(element);
};
$scope.remove = function (array, element) {
var index = array.indexOf(element);
if (index === -1) {
return false;
}
array.splice(index, 1);
};
$scope.moveUp = function (array, element) {
var index = array.indexOf(element);
if (index === -1) {
return false;
}
if (array[index - 1]) {
array.splice(index - 1, 2, array[index], array[index - 1]);
} else {
return 0;
}
};
$scope.moveDown = function (array, element) {
var index = array.indexOf(element);
if (index === -1) {
return false;
}
if (array[index + 1]) {
array.splice(index, 2, array[index + 1], array[index]);
} else {
return 0;
}
};
</pre>
<br />
You can view the working version here:</div>
<iframe border="0" height="600" src="http://kmturley.github.io/angular-sir-trevor/src/" width="100%"></iframe>
<a href="http://kmturley.github.io/angular-sir-trevor/">http://kmturley.github.io/angular-sir-trevor/</a><br />
<br />
And get the source code at:<br />
<a href="https://github.com/kmturley/angular-sir-trevor">https://github.com/kmturley/angular-sir-trevor</a></div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-29738324027580705102015-06-11T15:16:00.001-07:002015-06-17T07:31:31.694-07:00CSS only parallax using perspective and transform3d<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4gtOItgNFlndHbj_KYVrNVXGrydwjwWWF-kRj97gQSkoq6iin2cdtf5f-JeEpzAbSSCtXgvOME6HkD2QWOp8XRrqfGTqf6sMLBnMxfhpgZe1Zml6zXq_IswiSDeHzxK32YY_SsxU6GYY/s1600/parallax.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="160" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4gtOItgNFlndHbj_KYVrNVXGrydwjwWWF-kRj97gQSkoq6iin2cdtf5f-JeEpzAbSSCtXgvOME6HkD2QWOp8XRrqfGTqf6sMLBnMxfhpgZe1Zml6zXq_IswiSDeHzxK32YY_SsxU6GYY/s200/parallax.jpg" width="200" /></a></div>
When building single-page sites, generally we rely on javascript plugins to create effects for images and animations. Here we are going to look at a simple header parallax effect using only css<br />
<br />
<a name='more'></a>To create a parallax effect using css we can utilise 3d transforms combined with perspective to create layers of content. However these layers will stay at the same position and angle unless we can make the perspective relative to the scroll position. That's the trick!<br />
<br />
Caveats to this approach:<br />
<ul style="text-align: left;">
<li>You need to change html scrolling to body/div scrolling, which could affect other plugin/effects</li>
<li>If you want fixed elements above your parallax image, you have to put them outside the scrolling area</li>
<li>Touch scrolling for iOS devices is not supported in the same area as 3d transform, so you have to pick one or the other</li>
</ul>
1) 3d transforms are not supported on the html element, so our first step is to disable the scrolling fit the height to 100% to ensure the inner elements can use 100% height.<br />
<br />
<pre class="brush:css">html,
body {
height: 100%;
overflow: hidden;
}
</pre>
<br />
2) For this example I would like to have a fixed header on top of the parallax image, so we need have the header html outside of the scrolling area:<br />
<br />
<pre class="brush:html"><div class="header">
<p>logo</p>
</div>
</pre>
<br />
And some css for the header:
<br />
<br />
<pre class="brush:css">.header {
background-color: #fff;
position: fixed;
width: 100%;
z-index: 3;
}
</pre>
<br />
3) Now we need add the scrollable area below the fixed header<br />
<br />
<pre class="brush:html"><div class="header">
<p>logo</p>
</div>
<div class="container">
<div class="banner">
<img src="http://5d48184523c8a489ed05-91a4b8ed85c04e5358f91889505a4163.r43.cf1.rackcdn.com/6/4/large.jpg" alt="" class="image" />
</div>
<div class="content">
<p>rest of page content</p>
</div>
</div>
</pre>
<br />
and add some css to make the container scrollable and have start the 3d perspective. <b>Note: using -webkit-overflow-scrolling: touch; will prevent the 3d transforms from working properly.</b><br />
<br />
<pre class="brush:css">.container {
height: 100%;
-webkit-perspective: 100px;
perspective: 100px;
-webkit-perspective-origin: 0 0;
perspective-origin: 0 0;
overflow-x: hidden;
overflow-y: auto;
}
</pre>
<br />
4) If you have any additional div containers between the scrolling container and the image itself, you will need to use preserve-3d to retain the perspective. So in this case we will add the css to our banner area:<br />
<br />
<pre class="brush:css">.banner {
position: relative;
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d;
z-index: -1;
}
</pre>
<br />
5) Last step is to move the image back in 3d space, and scale it back to fit the view:<br />
<br />
<pre class="brush:css">.banner .image {
-webkit-transform: translateZ(-50px) scale(2);
transform: translateZ(-50px) scale(2);
width: 100%;
}
</pre>
<br />
Now you have a working parallax banner. You can see it working here:<br />
<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="600" src="//jsfiddle.net/kmturley/258fzptt/11/embedded/result,html,css" width="100%"></iframe>
<a href="http://jsfiddle.net/kmturley/258fzptt/11/" target="_blank">http://jsfiddle.net/kmturley/258fzptt/11/</a></div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-18165002577380834312015-05-28T09:33:00.001-07:002015-05-29T14:08:04.276-07:00CSS vertical rhythm with typography units<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibBDD91qsV5zJ8wfmDgPmggyJAs0owe0nGh7r2dmwjQRl9-sfyw2W1OCsg_UjyEDYUW4kWl1fNWovKafZ7LbhtLTb6g0K0Kwdt11SRMF9Tf1PXDmHfJhtbvLT83NcHkGKskg1F3ouaVV0/s1600/typography.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="145" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibBDD91qsV5zJ8wfmDgPmggyJAs0owe0nGh7r2dmwjQRl9-sfyw2W1OCsg_UjyEDYUW4kWl1fNWovKafZ7LbhtLTb6g0K0Kwdt11SRMF9Tf1PXDmHfJhtbvLT83NcHkGKskg1F3ouaVV0/s200/typography.jpg" width="200" /></a></div>
One of the issues frontend developers have when creating their base grid is setting up the typography. Web typography is not consistent across browsers and very hard to make pixel perfect. Here are some simple steps to ensure the vertical rhythm of the site is consistent.<br />
<a name='more'></a><b><br /></b>
<b>1) Define a baseline unit size</b><br />
We want to define a unit which matches our font-size. So for example if your default font size is 16px. Your baseline unit size could be 16px, 8px, 4px or 2px depending on the accuracy you need. For this we will use 8px and a line-height of 3 x 8px = 24px:<br />
<br />
<pre class="brush:css">/*
* Simple Web Typography
* base = 8px = 0.5em
* font = 16px = 1em
* line = 24px = 1.5em
*/
body {
font-family: sans-serif;
font-size: 16px;
line-height: 1.5em;
margin: 0;
}
</pre>
<br />
I've decided to leave the font-size at a pixel value to ensure compatibility cross browser and to make it clear to developers what the base size is.<br />
<br />
<b>2) Set any other font sizes needed first</b><br />
The headings and other styles need to be set, so they are inline with the base unit size. If the font-sizes are not correct, the line-heights and margins will also be off, because they inherit from the font-size using em's:<br />
<br />
<pre class="brush:css">h1 { font-size: 3.5em; } /* 16px x 3.5 = 56px */
h2 { font-size: 3em; } /* 16px x 3 = 48px */
h3 { font-size: 2.5em; } /* 16px x 2.5 = 40px */
h4 { font-size: 2em; } /* 16px x 2 = 32px */
h5 { font-size: 1.5em; } /* 16px x 1.5 = 24px */
h6 { font-size: 1em; } /* 16px x 1 = 16px */
</pre>
<br />
<b>3) Reset the line-heights and margins</b><br />
We also need to manually reset the line-heights and margins between type. <b>NOTE: using em's for line-height and margin are relative to the current font size. So 1em does not equal 16px, it equals the font size. So always set the font-size first, or opt for using rem's which are relative to the base font-size:</b><br />
<br />
<pre class="brush:css">h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: normal;
line-height: 1em; /* relative to font size */
}
h1,
h2,
h3,
h4,
h5,
h6,
p {
margin: 0 0 0.5rem 0; /* relative to base font-size */
}
</pre>
<br />
<br />
If you don't want to use inheritance of font-sizes you can opt for rem's instead of em's. This is supported in IE9 and above:<br />
<a href="http://caniuse.com/#search=rem">http://caniuse.com/#search=rem</a><br />
<br />
These three techniques will get a basic vertical rhythm going in your site which is accurate enough to look great. Here is a grid showing it all put together:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibBDD91qsV5zJ8wfmDgPmggyJAs0owe0nGh7r2dmwjQRl9-sfyw2W1OCsg_UjyEDYUW4kWl1fNWovKafZ7LbhtLTb6g0K0Kwdt11SRMF9Tf1PXDmHfJhtbvLT83NcHkGKskg1F3ouaVV0/s1600/typography.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="465" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibBDD91qsV5zJ8wfmDgPmggyJAs0owe0nGh7r2dmwjQRl9-sfyw2W1OCsg_UjyEDYUW4kWl1fNWovKafZ7LbhtLTb6g0K0Kwdt11SRMF9Tf1PXDmHfJhtbvLT83NcHkGKskg1F3ouaVV0/s640/typography.jpg" width="640" /></a></div>
<br />
<br />
If you are planning to put your type inside panels with background colours and padding. Then you will also encounter another challenge: How to make the padding even all the way around the panel with these margins.<br />
<br />
<pre class="brush:css">.panel {
margin: 0 0 1rem 0; /* add margin to our panel */
padding: 2em; /* set even padding no matter on the content */
}
.panel *:last-child {
margin-bottom: 0; /* remove the margin from the last item inside the panel */
}
</pre>
<br />
You can see a full working example here:<br />
<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="300" src="//jsfiddle.net/kmturley/5w0e2z56/9/embedded/result,html,css" width="100%"></iframe>
<a href="http://jsfiddle.net/kmturley/5w0e2z56/9/" target="_blank">http://jsfiddle.net/kmturley/5w0e2z56/9/</a></div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-8314802631464867052015-05-12T08:36:00.001-07:002015-05-12T08:40:28.593-07:00Using inline images as background images with dynamic height<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3uyNK_qm79FQ_0efqO2nvPL-WkGPzAfC5o8Sld96CKdkJK-P71II0hOz22-OVZ40yt7e5D884T5wj3GyfMrKz2BzPcTCqg9rIp1syvFZbwdHDh1V1_uBB9cdrYcK_CMTym7jthOT9ooQ/s1600/background-image.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="120" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3uyNK_qm79FQ_0efqO2nvPL-WkGPzAfC5o8Sld96CKdkJK-P71II0hOz22-OVZ40yt7e5D884T5wj3GyfMrKz2BzPcTCqg9rIp1syvFZbwdHDh1V1_uBB9cdrYcK_CMTym7jthOT9ooQ/s200/background-image.jpg" width="200" /></a></div>
CSS background images provide great browser performance and allow you position and resize images to cover or fit to a box frame. However sometimes as a developer you might need to use inline images due to limitations out of your control. Here is an example of how that can be achieved.<br />
<br />
<a name='more'></a><br />
There are some situations where background images might not work e.g.<br />
<ul style="text-align: left;">
<li>Your images come from a database</li>
<li>Your CMS already outputs the images as img html tags</li>
<li>You have Content Security Policy or other security restrictions which disallow inline css</li>
</ul>
<div>
So how can we turn a regular image html tag into a resized background image? First we need our image tag as html:</div>
<div>
<br /></div>
<pre class="brush:html"><img src="http://fc09.deviantart.net/fs71/i/2013/257/5/c/a_photograph_for_the_skybridge_by_alexsky0-d6m8uo3.jpg" />
</pre>
<div>
<br /></div>
<div>
To make this a background image we should put a wrapper around it:<br />
<br /></div>
<pre class="brush:html"><div class="image">
<img src="http://fc09.deviantart.net/fs71/i/2013/257/5/c/a_photograph_for_the_skybridge_by_alexsky0-d6m8uo3.jpg" />
</div>
</pre>
<div>
<br />
We can add the following css to position the image absolutely relative to the parent wrapper:<br />
<br />
<pre class="brush:css">.image {
position: relative;
height: 300px;
overflow: hidden;
}
.image img {
position: absolute;
min-height: 100%;
min-width: 100%;
}
</pre>
<br />
You can see this working here:<br />
<a href="http://jsfiddle.net/kmturley/vftq8zs1/">http://jsfiddle.net/kmturley/vftq8zs1/</a><br />
<br />
But what if we don't want to fix the height of the parent? What if we want to fit the height of the content inside, whatever the length? How do we also center the image so the most important parts are always show?<br />
<br />
The trick here is to use display table-cell. Update your html to have two columns with text and an image:<br />
<br />
<pre class="brush:html"><div class="t">
<div class="tc image">
<img src="http://fc09.deviantart.net/fs71/i/2013/257/5/c/a_photograph_for_the_skybridge_by_alexsky0-d6m8uo3.jpg" />
</div>
<div class="tc desc">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc eu suscipit quam. Sed pellentesque lobortis leo eu mattis. Ut nec tempor eros. Mauris pellentesque, lacus a convallis pretium, ipsum ante cursus mauris, sed efficitur metus ante eget turpis.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc eu suscipit quam. Sed pellentesque lobortis leo eu mattis. Ut nec tempor eros. Mauris pellentesque, lacus a convallis pretium, ipsum ante cursus mauris, sed efficitur metus ante eget turpis.</p>
</div>
</div>
</pre>
<br />
And now we can change our css to add a table and table-cell properties to the divs:<br />
<br />
<pre class="brush:css">.t {
display: table;
table-layout: fixed;
width: 100%;
}
.tc {
vertical-align: middle;
}
.image {
position: relative;
overflow: hidden;
}
.image img {
position: absolute;
min-height: 100%;
min-width: 100%;
}
</pre>
<br />
Finally to make the image centered within the table cell we need to add vertical align: middle and left offsets:<br />
<br />
<pre class="brush:css">.t {
display: table;
table-layout: fixed;
width: 100%;
}
.tc {
display: table-cell;
vertical-align: middle;
}
.image {
position: relative;
overflow: hidden;
}
.image img {
position: absolute;
min-height: 100%;
min-width: 100%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
</pre>
<br />
<br /></div>
<div>
Here is a working example of it in action:</div>
<div>
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="300" src="//jsfiddle.net/kmturley/dw77svbh/17/embedded/result,html,css" width="100%"></iframe>
<a href="http://jsfiddle.net/kmturley/dw77svbh/17/">http://jsfiddle.net/kmturley/dw77svbh/17/</a></div>
</div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-1151060717831115882015-04-06T15:18:00.005-07:002015-07-22T14:40:49.069-07:00Recording, syncing and exporting web audio<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmi7VZWporFVAhwiw_1rzqL1qULC96fXk_xfyqG1NjMBBDyIur23d2Vj7HrriB9R3YtJr-rNwE8aZp-4WgbIjs5IdiGcu-kLReQHDq7yLUBzcZfQLskI80-N80Tx8SxieZGteexsvlkuM/s1600/audio.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmi7VZWporFVAhwiw_1rzqL1qULC96fXk_xfyqG1NjMBBDyIur23d2Vj7HrriB9R3YtJr-rNwE8aZp-4WgbIjs5IdiGcu-kLReQHDq7yLUBzcZfQLskI80-N80Tx8SxieZGteexsvlkuM/s1600/audio.jpg" height="177" width="200" /></a></div>
I'm currently working on a side project which allows musicians to collaborate and record music. For this web app I needed to overcome several technical challenges which I will explain for you today!<br />
<br />
<a name='more'></a><br />
<b>1) Supporting audio recording/playback</b><br />
The first challenge is how to support web audio playback for as many people as possible. Bearing in mind Web Audio technologies are fairly new to the scene and not all features are supported. First step is to look at the supported browsers for each technology:<br />
http://caniuse.com/#search=audio<br />
<br />
I settled on Web Audio as it is supported by the most popular browsers for desktop and mobile, Chrome, Firefox and Safari. It also provides an array of advanced features which I could use to make my web app better!<br />
<br />
For my version I decided to use Recorder.js to assist interactions with the Web Audio API. It provides many advantages such as using Web Workers, which prevent glitches and jumps in audio recording.<br />
<br />
You can get Recorder.js here:<br />
https://github.com/mattdiamond/Recorderjs<br />
<br />
Here is my code to check for Web Audio API support and then to load Recorder.js library:<br />
<br />
<pre class="brush:js">var me = this;
window.onload = function () {
// check for web audio support
try {
window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext;
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
me.context = new window.AudioContext();
me.context.createGain = me.context.createGain || me.context.createGainNode;
} catch (e) {
window.alert('Your browser does not support WebAudio, try Google Chrome');
}
// if recording is supported then load Recorder.js
if (navigator.getUserMedia) {
navigator.getUserMedia({audio: true}, function (stream) {
var input = me.context.createMediaStreamSource(stream);
me.recorder = new Recorder(input);
}, function (e) {
window.alert('Please enable your microphone to begin recording');
});
} else {
window.alert('Your browser does not support recording, try Google Chrome');
}
};
</pre>
<br />
<br />
<b>2) Preloading audio files</b><br />
The next challenge is how to preload audio files in the background. This allows them to be played on demand e.g. in time with a button click or another sound. You can accomplish this by loading the file using a normal XHR request, then use the Web Audio API to decode the data back into a audio buffer array for fast playback:<br />
<br />
<pre class="brush:js">cue: function (url, callback) {
// abort the previous ajax request
var me = this;
if (this.request) {
this.request.abort();
} else {
this.request = new XMLHttpRequest();
}
this.request.open('GET', url, true);
this.request.responseType = 'arraybuffer';
this.request.onload = function () {
// convert data into audio buffer
me.context.decodeAudioData(me.request.response, function (buffer) {
callback(buffer);
});
};
this.request.send();
}
</pre>
<br />
<b>3) Playing and looping audio</b><br />
Next we want to play back and loop the audio buffers! In this example I am also recording the start time of the audio. This is important for later on when we want to sync another audio file to this one:<br />
<br />
<pre class="brush:js">play: function (data, callback) {
// create audio node and play buffer
var me = this,
source = this.context.createBufferSource(),
gainNode = this.context.createGain();
if (!source.start) { source.start = source.noteOn; }
if (!source.stop) { source.stop = source.noteOff; }
source.connect(gainNode);
gainNode.connect(this.context.destination);
source.buffer = data;
source.loop = true;
source.startTime = this.context.currentTime; // important for later!
source.start(0);
return source;
}
</pre>
<br />
<br />
<b>4) Recording audio</b><br />
Now we have a backing track looping we want to be able to record a vocal over the top. For this we are using Recorder.js's Web Workers, again i'm manually saving the start time for use later on:<br />
<br />
<pre class="brush:js">record: function () {
// start recording using Recorder.js
this.recorder.clear();
this.recorder.startTime = this.context.currentTime;
this.recorder.record();
}
</pre>
<br />
<br />
<b>5) Stop recording and export buffer</b><br />
When we stop the recording, we can export the buffer using Recorder.js ready for playback<br />
<br />
<pre class="brush:js">recordStop: function (callback) {
// stop recording and get the recorded buffer data
this.recorder.stop();
this.recorder.getBuffer(function (buffers) {
callback(buffers);
});
}
</pre>
<br />
<br />
<b>6) Play audio in sync with another file</b><br />
We now need to time the recorded audio buffer to play back exactly in time with the loop. How do we do that? I created a sync function which calculates the difference in time between the current time, and the length of the loop you pass in. This uses the custom start time variable from the play function in step 3. Then it runs the function after a delay, to put it in sync with the loop.<br />
<br />
<pre class="brush:js">sync: function (action, target, param, runThis) {
// calculate difference between current time and loop length
var me = this,
offset = (this.context.currentTime - target.startTime) % target.buffer.duration,
time = target.buffer.duration - offset;
// clear previous timers then run function after time difference
if (this.syncTimer) {
window.clearTimeout(this.syncTimer);
}
this.syncTimer = window.setTimeout(function () {
runThis();
}, time * 1000);
}</pre>
<br />
<b>7) Adjusting for latency and small timing issues</b><br />
Now we have working playback, where both audio files start at the same time. But now we have another problem, different devices and browsers have a slight delay between the sound and recording to disk, even thought the audio files start at the same time! We need a way to tweak the recording, pushing it faster or slower to match the original loop.<br />
<br />
There are several approaches to this, including getting the user to set the offset before recording and adjusting the timers. However I opted to go for the best user experience, which is to complete the recording, then allow the user to adjust the recording sync dynamically during playback. Quite a tough challenge!<br />
<br />
The way I achieved this was to force the recording to be shorter than the original loop length. Then dynamically fill in the gaps at the start and end with blank audio. These gaps can then be adjusted dynamically, based on a slider.<br />
<br />
The first function allows us to create an audio buffer from audio data, this is required to work out the real length of the audio based on the playback sample rate:<br />
<br />
<pre class="brush:js">createBuffer: function (buffers, channelTotal) {
// create an audio buffer from data, so we can play it back and get the real length
var channel = 0,
buffer = this.context.createBuffer(channelTotal, buffers[0].length, this.context.sampleRate);
for (channel = 0; channel < channelTotal; channel += 1) {
buffer.getChannelData(channel).set(buffers[channel]);
}
return buffer;
}
</pre>
<br />
The next function works out the different in length (in samples) between the original loop and the recording. This gives us the before and after samples, we then deduct the offset that the user can change to put more samples at the start or at the end:<br />
<br />
<pre class="brush:js">getOffset: function (vocalsRecording, backingInstance, offset) {
// work out the difference in samples between the length of two recordings
var diff = (this.recorder.startTime + (offset / 1000)) - backingInstance.startTime;
return {
before: Math.round((diff % backingInstance.buffer.duration) * this.context.sampleRate),
after: Math.round((backingInstance.buffer.duration - ((diff + vocalsRecording.duration) % backingInstance.buffer.duration)) * this.context.sampleRate)
};
}
</pre>
<br />
The last function takes the recorded audio data, and the number of before and after samples. It creates a new audio data array and fills in the blanks before and after the recording:<br />
<br />
<pre class="brush:js">offsetBuffer: function (vocalsBuffers, before, after) {
// create a new audio buffer and fill in the gaps before and after of the recording
var i = 0,
channel = 0,
channelTotal = 2,
num = 0,
audioBuffer = this.context.createBuffer(channelTotal, before + vocalsBuffers[0].length + after, this.context.sampleRate),
buffer = null;
for (channel = 0; channel < channelTotal; channel += 1) {
buffer = audioBuffer.getChannelData(channel);
for (i = 0; i < before; i += 1) {
buffer[num] = 0;
num += 1;
}
for (i = 0; i < vocalsBuffers[channel].length; i += 1) {
buffer[num] = vocalsBuffers[channel][i];
num += 1;
}
for (i = 0; i < after; i += 1) {
buffer[num] = 0;
num += 1;
}
}
return audioBuffer;
}</pre>
<br />
Whew... that's a lot of maths. But amazingly it works!<br />
<br />
<b>8) Exporting the modified audio to wav</b><br />
Once the user is happy with the recording and the offset is lining up correctly, they want save it as an audio file. For this we need to export the modified audio data and adjust the sample rate to change the filesize. Recorder.js unfortunately doesn't support passing our modified audio file through to web workers and also doesn't support different sample rates, so we will need to modify recorderWorker.js to support our new features.<br />
<br />
I added before and after params to the export wav function and the rate variable allows you to drop the sample rate down to reduce the quality/filesize:<br />
<br />
<pre class="brush:js">var rate = 22050;
function exportWAV(type, before, after){
if (!before) { before = 0; }
if (!after) { after = 0; }
var channel = 0,
buffers = [];
for (channel = 0; channel < numChannels; channel++){
buffers.push(mergeBuffers(recBuffers[channel], recLength));
}
var i = 0,
offset = 0,
newbuffers = [];
for (channel = 0; channel < numChannels; channel += 1) {
offset = 0;
newbuffers[channel] = new Float32Array(before + recLength + after);
if (before > 0) {
for (i = 0; i < before; i += 1) {
newbuffers[channel].set([0], offset);
offset += 1;
}
}
newbuffers[channel].set(buffers[channel], offset);
offset += buffers[channel].length;
if (after > 0) {
for (i = 0; i < after; i += 1) {
newbuffers[channel].set([0], offset);
offset += 1;
}
}
}
if (numChannels === 2){
var interleaved = interleave(newbuffers[0], newbuffers[1]);
} else {
var interleaved = newbuffers[0];
}
var downsampledBuffer = downsampleBuffer(interleaved, rate);
var dataview = encodeWAV(downsampledBuffer, rate);
var audioBlob = new Blob([dataview], { type: type });
this.postMessage(audioBlob);
}
</pre>
<br />
There you have it, a completed audio workflow!<br />
<br />
If you want to have a play with some live code you can see my working demo here:<br />
<a href="http://kmturley.github.io/Recorderjs/loop.html">http://kmturley.github.io/Recorderjs/loop.html</a></div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-22759465581001333362015-02-11T14:46:00.001-08:002015-02-11T14:48:10.235-08:00Responsive css only carousel<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1z2WPbVQ2fCbc0T0WywYMLZGavMI5po14cLeoALKUwEoeG23uKd6LVRqoyr7w3zOuO930eDhv1WBphjBOZkyQIdPqtcG5qLqu3b2GktvawKPA_S0PRE_kU6DuTQODSlv3QQGeo9rUf_Y/s1600/carousel.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1z2WPbVQ2fCbc0T0WywYMLZGavMI5po14cLeoALKUwEoeG23uKd6LVRqoyr7w3zOuO930eDhv1WBphjBOZkyQIdPqtcG5qLqu3b2GktvawKPA_S0PRE_kU6DuTQODSlv3QQGeo9rUf_Y/s1600/carousel.jpg" height="121" width="200" /></a></div>
A while ago I looked at some methods to create css only carousels. Since then new css features have become widespread allowing us to do more. Here i'm revisiting the css carousel to see what we can achieve with very simple code!<br />
<a name='more'></a><br />
<br />
<br />
First off we need to define some requirements for our carousel. This will give us our list of features:<br />
<ul style="text-align: left;">
<li>Must be able to fill any width or height (responsive)</li>
<li>Multiple slides</li>
<li>Ability to switch between slides</li>
<li>Ideally using browser urls to allow linking directly to slides</li>
<li>Slide controls to indicate which slides we are on</li>
</ul>
Now we know what we are doing, lets start off by creating some html.<br />
<br />
<pre class="brush: html"><div class="carousel">
<div class="item"><h1>Item 1</h1></div>
<div class="item"><h1>Item 2</h1></div>
<div class="item"><h1>Item 3</h1></div>
<div class="item"><h1>Item 4</h1></div>
</div>
</pre>
<br />
And add some css to make the carousel full width/height and slides fit the space:<br />
<br />
<pre class="brush: css">.carousel {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #333;
}
.carousel .item {
position: absolute;
width: 100%;
height: 100%;
text-align: center;
}
</pre>
<br />
Now we need a way of animating the slides in and out. I've name-spaced this as a separate class slide-in, which you will need to add to slides that you want to animate in:<br />
<br />
<pre class="brush: css">.carousel .slide-in {
-webkit-transform: translate3d(-90%, 0px, 0px);
-moz-transform: translate3d(-90%, 0px, 0px);
-ms-transform: translate3d(-90%, 0px, 0px);
-o-transform: translate(-90%, 0px, 0px);
transform: translate3d(-90%, 0px, 0px);
-webkit-transition: -webkit-transform 0.5s ease-out;
-moz-transition: -moz-transform 0.5s ease-out;
-ms-transition: -ms-transform 0.5s ease-out;
-o-transition: -o-transform 0.5s ease-out;
transition: transform 0.5s ease-out;
z-index: 1;
}
.carousel .slide-in:target ~ .slide-in {
-webkit-transform: translate3d(90%, 0px, 0px);
-moz-transform: translate3d(90%, 0px, 0px);
-ms-transform: translate3d(90%, 0px, 0px);
-o-transform: translate(90%, 0px, 0px);
transform: translate3d(90%, 0px, 0px);
}
.carousel .slide-in:target {
-webkit-transform: translate3d(0px, 0px, 0px);
-moz-transform: translate3d(0px, 0px, 0px);
-ms-transform: translate3d(0px, 0px, 0px);
-o-transform: translate(0px, 0px, 0px);
transform: translate3d(0px, 0px, 0px);
z-index: 3;
}
.carousel .slide-in:target + .slide-in {
z-index: 2;
}
</pre>
<br />
To activate a slide as the current slide you need to add id's to all of your slide items. These id's can then be targeted via the hash url:<br />
<br />
<pre class="brush: html"><div class="carousel">
<div class="item slide-in" id="item1"><h1>Item 1</h1></div>
<div class="item slide-in" id="item2"><h1>Item 2</h1></div>
<div class="item slide-in" id="item3"><h1>Item 3</h1></div>
<div class="item slide-in" id="item4"><h1>Item 4</h1></div>
</div>
</pre>
<br />
So now you should be able to add #item1 to your url and see the item change. Next we need to add some controls so you don't need to type the url:<br />
<br />
<pre class="brush: html"><div class="carousel">
<div class="item slide-in" id="item1"><h1>Item 1</h1></div>
<div class="item slide-in" id="item2"><h1>Item 2</h1></div>
<div class="item slide-in" id="item3"><h1>Item 3</h1></div>
<div class="item slide-in" id="item4"><h1>Item 4</h1></div>
<div class="controls">
<a href="#item1" class="btn"></a>
<a href="#item2" class="btn"></a>
<a href="#item3" class="btn"></a>
<a href="#item4" class="btn"></a>
</div>
</div>
</pre>
<br />
By clicking a control, it will changed the has url to an id, which will target the correct css and show the slide. Lets add some styling to the controls so they align to the bottom and center of the carousel:<br />
<br />
<pre class="brush: css">.carousel .controls {
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
z-index: 5;
}
.carousel .controls .btn {
display: inline-block;
background-color: #fff;
-webkit-border-radius: 100%;
-moz-border-radius: 100%;
border-radius: 100%;
width: 12px;
height: 12px;
vertical-align: middle;
margin: 0px 1px 15px;
opacity: 0.7;
}
.carousel .controls .btn:hover {
opacity: 0.9;
}
</pre>
<br />
There are only two potential limitations to this approach I can see so far. To select the first slides the default, you need to reverse the order of the slides and the animation (see jsfiddle url below). The tab indexes must be set to allow accessibility and requires more html.<br />
<br />
You can view a working example here:<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="300" src="//jsfiddle.net/kmturley/fs6wge3f/10/embedded/result,html,css" width="100%"></iframe>
</div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-72788673134190138342015-01-22T13:26:00.002-08:002015-01-22T13:30:24.926-08:00Wordpress, Timber & Twitter Bootstrap<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEimZ6-jMdrnR1zKlyHtbfOcDyQzIzKxe4NC-gz1TOejiX0AYWLN57l68IlZ_JTKneQzXIZQTtJXz8wZso9TZuqkx3ET4QV4OF2r6zIoa7YQWPQgmvsRdcs9cnj_j533yFCQms0N2Mpiw68/s1600/timber-wordpress-bootstrap.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEimZ6-jMdrnR1zKlyHtbfOcDyQzIzKxe4NC-gz1TOejiX0AYWLN57l68IlZ_JTKneQzXIZQTtJXz8wZso9TZuqkx3ET4QV4OF2r6zIoa7YQWPQgmvsRdcs9cnj_j533yFCQms0N2Mpiw68/s1600/timber-wordpress-bootstrap.jpg" height="138" width="200" /></a></div>
Wordpress is one of the most popular CMS blogging platforms on the internet today, with the latest updates it's really improved in terms of user experience for the admin user. However there is still one massive pain for developers in the template system....where there isn't really one!<br />
<br />
<a name='more'></a>Thankfully there is a great solution for this, by installing the Timber Wordpress plugin you can use the familiar Twig template language which separates the server side code from the view cleanly.<br />
<br />
Looking out there there weren't many examples of templates for Timber, and none including Twitter Bootstrap...so I've decided to make one! Here is a guide on how to set it all up yourself:<br />
<br />
1) First setup is to go to <a href="http://www.wordpress.org/">www.wordpress.org</a> and download the package. Extract the files into a folder in your web server e.g. http://localhost:8888/wordpress-example/<br />
<br />
2) Follow the installation instructions for Wordpress. Afterwards in the admin go to Plugins > Add New and search for 'Timber By Jared Novack + Upstatement'.<br />
<br />
3) After installing the Timber plugin you will need to either:<br />
a) create a new theme containing your templates<br />
b) download <a href="https://github.com/kmturley/timber-bootstrap" target="_blank">my theme from Github</a> and place it within your themes folder at:<br />
<pre class="brush:html">/wp-content/themes/timber-bootstrap</pre>
<br />
4) Now you need to navigate in the admin to 'Appearance' and activate the theme.<br />
<br />
<b>Creating a Timber theme</b><br />
To create a theme for Timber you can follow their <a href="https://github.com/jarednova/timber/tree/master/timber-starter-theme" target="_blank">starter template on Github</a>. I've listed out some instructions on a different method where your template modules are sorted by functionality which is much better for larger projects!<br />
<br />
1) First step is to create a new folder inside your Wordpress folders e.g:<br />
<pre class="brush:html">/wp-content/themes/timber-bootstrap</pre>
<br />
2) Inside this folder create an index.php file which contains the code to switch the template based upon the page type:<br />
<br />
<pre class="brush:php">if (!class_exists('Timber')) {
echo 'Timber not activated. Make sure you activate the plugin in <a href="https://www.blogger.com/wp-admin/plugins.php#timber">/wp-admin/plugins.php</a>';
return;
}
if (is_singular()) {
$context = Timber::get_context();
$context['menu'] = new TimberMenu();
$context['post'] = new TimberPost();
} else {
$context = Timber::get_context();
$context['menu'] = new TimberMenu();
$context['posts'] = Timber::get_posts();
}
if (is_single()) {
$template = 'blog-post/blog-post';
} else if (is_page()) {
$template = 'page/page';
} else if (is_home()) {
$template = 'blog/blog';
} else if (is_category()) {
$template = 'app/app';
} else if (is_tag()) {
$template = 'app/app';
} else if (is_author()) {
$template = 'app/app';
}
Timber::render('modules/'.$template.'.twig', $context);
</pre>
<br />
3) If you want to support extra features such as thumbnails, you'll also need a functions.php file continaing the additional code. Here is an example adding thumbnails to your blog posts<br />
<br />
<pre class="brush:php">add_theme_support('post-thumbnails');</pre>
<br />
4) Wordpress themes require a third file in your folder, which gives more information about your theme. Add a style.css with some comments:<br />
<br />
<pre class="brush:css">/*
Theme Name: Timber Bootstrap
Description: Example template using Timber and Twitter Bootstrap together
Author: kmturley
*/</pre>
<br />
5) Now you are ready to create some templates! I've decided to organise mine by functionality rather than type. This means the css, javascript and the template are grouped in the same folder (making it easier to move modules between projects). This is the structure i'm using:<br />
<br />
<pre class="brush:html">modules/ --> modules grouped by functionality
app/ --> main application module
app.css
app.js
app.twig
blog/ --> example blog module with css, js and template
blog.css
blog.js
blog.twig</pre>
<br />
6) In the app.twig file I've put my base html template using content blocks which the other templates can override:<br />
<br />
<pre class="brush:html"><!doctype html>
<html {{ site.language_attributes }}>
<head>
<meta charset="{{ site.charset }}" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="{{ site.description }}" />
<title>{{ site.title }}</title>
<link rel="icon" href="{{ site.theme.link }}/img/favicon.ico" />
<link href="{{ site.theme.link }}/libs/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="{{ site.theme.link }}/modules/app/app.css" rel="stylesheet" />
{% block head %}
{% endblock %}
</head>
<body class="app">
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{{ site.url }}">{{ site.title }}</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
{% for item in menu.get_items %}
<li class="{{ item.classes | join(' ') }}"><a href="{{ item.get_link }}">{{ item.title }}</a></li>
{% endfor %}
</ul>
</div>
</div>
</nav>
<div class="container">
{% block content %}
Template not found
{% endblock %}
</div>
<script src="{{ site.theme.link }}/libs/jquery/jquery-1.11.2.min.js"></script>
<script src="{{ site.theme.link }}/libs/bootstrap/bootstrap.min.js"></script>
<script src="{{ site.theme.link }}/modules/app/app.js"></script>
{% block foot %}
{% endblock %}
</body>
</html></pre>
<br />
7) Within your templates there are many useful tags you can use such as:<br />
<pre class="brush:html">{{ site.title }} = output the site title
{{ site.url }} = link to the site root
{{ site.theme.link }} = output the theme folder url
{% block content %}{% endblock %} = create a named block which can be overriden by child templates</pre>
<br />
8) Create more templates!<br />
<br />
Hopefully that's helpful, if you want to use my project as a starting point then i've put it here:<br />
<a href="https://github.com/kmturley/timber-bootstrap">https://github.com/kmturley/timber-bootstrap</a></div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com1tag:blogger.com,1999:blog-9186252211939314092.post-14817917546977690532015-01-05T15:03:00.003-08:002018-01-02T10:05:29.664-08:00Responsive and adaptive images using focus points<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5DMSy0T6piUl2SNAKWm3cAU3TmY41flEck0iySRCIqwPsS3o5h_8PgQJDU0H90hJLd8xFa_6i0G-PJ78qeHlVVSblLzQbdODBh7atOEoeCEdTIJMABnKNyAz01ACdWf6Nx_dCNtu9-m0/s1600/Screen+Shot+2018-01-02+at+1.01.39+PM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="883" data-original-width="1600" height="176" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5DMSy0T6piUl2SNAKWm3cAU3TmY41flEck0iySRCIqwPsS3o5h_8PgQJDU0H90hJLd8xFa_6i0G-PJ78qeHlVVSblLzQbdODBh7atOEoeCEdTIJMABnKNyAz01ACdWf6Nx_dCNtu9-m0/s320/Screen+Shot+2018-01-02+at+1.01.39+PM.png" width="320" /></a></div>
<span style="text-align: left;">Devices and computers come in millions of shapes and sizes it's very hard as a web developer to make your images fit and look good at every size. In this post i'm going to look at a second solution which uses focal points to tell the browser what should be on screen.</span>
<br />
<a name='more'></a>Responsive and adaptive images can be implemented in several ways and there are advantages and disadvantages to each method. Here are just a few of those:<br />
<br />
<ul style="text-align: left;">
<li>Background image set with css to cover the area (fits to content height)</li>
<li>Responsive image library such as <a href="https://github.com/scottjehl/picturefill" target="_blank">picturefill</a></li>
<li>Replace the image src using css content:src for different media queries (see my previous blog post)</li>
</ul>
<div>
However these solutions don't solve the main problem. How do we ensure the most important part of the photo is always shown in view? regardless of the format/size of the image?</div>
<div>
<br /></div>
<div>
Enter focus point solution!</div>
<div>
<ol style="text-align: left;">
<li>First choose the most important point of the image</li>
<li>Work out the coordinates as a percentage of the image width/height e.g. 24% from the left 48% form the top</li>
<li>Then add it as a background image to an area of the page and define the background position using the percentages</li>
</ol>
<pre class="brush: css;">.image {
background-image: url('https://i.pinimg.com/736x/17/30/42/17304288adec0f9350546e7a745305c9--antlers-elk.jpg');
background-position: 28% 89%;
}
</pre>
<div>
<br />
Now the image will position itself based upon your focal point, and not the center of the image!<br />
<br />
If you are using jQuery already in your page you can use this handy plugin called <a href="https://github.com/jonom/jquery-focuspoint" target="_blank">jQuery Focus Point</a> which will calculate the values for you.<br />
<br />
I wanted to use the idea on an AngularJS project, but couldn't find a solution so made my own which you can see working here:<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="600" src="https://jsfiddle.net/kmturley/607swqnh/27/embedded/result%2Chtml%2Ccss%2Cjs/" width="100%"></iframe>
<a href="https://jsfiddle.net/kmturley/607swqnh/27/">https://jsfiddle.net/kmturley/607swqnh/27/</a><br />
<br />
This solution works really well went integrated with a CMS. You ask the uploader to set one focal point for each image, and the images only need to be outputted in different sizes (no extra crops are needed)</div>
</div>
</div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-9304423091071885792014-12-15T14:03:00.001-08:002015-04-07T07:00:09.944-07:00Responsive images using css only<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg55NhXZQXiFtugfkmJ3AAt81O17KsAPpPOxcQ6nMumiHWItLacztjwwkriXGkhZ2DayOIji9JP2kgx66gU-gZA2OztO3Y2HHzKmKys9Fhemk0DDofi8H4wirnTANkcdAbX6edIeOJtN5o/s1600/320px-Photograph_of_1936_of_a_Cabin_Behind_the_Amoureaux_House_in_Ste_Genevieve_MO.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg55NhXZQXiFtugfkmJ3AAt81O17KsAPpPOxcQ6nMumiHWItLacztjwwkriXGkhZ2DayOIji9JP2kgx66gU-gZA2OztO3Y2HHzKmKys9Fhemk0DDofi8H4wirnTANkcdAbX6edIeOJtN5o/s1600/320px-Photograph_of_1936_of_a_Cabin_Behind_the_Amoureaux_House_in_Ste_Genevieve_MO.jpg" height="140" width="200" /></a></div>
One of the biggest challenges on responsive sites is handling images across different size screens and resolutions. Here we are going to look at the future support for it and also a simple method to solve this problem using css only.<br />
<a name='more'></a>When you are building a responsive site, you want to use high quality images to show off your content. However this means a tradeoff as the user needs a faster internet connection and ideally a larger screen to view the content. The solution to this problem is responsive images, where you can change the image size/quality based on the users device.<br />
<br />
There are plans to include responsive images in the new w3 specification for browsers, but as of 2014 it has patchy support: <a href="http://caniuse.com/#search=responsive">http://caniuse.com/#search=responsive</a><br />
<br />
Another solution out there is to use the Picture polyfill to bring support to more browsers: <a href="http://scottjehl.github.io/picturefill/">http://scottjehl.github.io/picturefill/</a> However this involves using the library, javascript and will require more processing power on a mobile browser to render the page. Lets look at a way we can do this with pure css!<br />
<br />
First off we need an image embedded into the page using html:<br />
<br />
<pre class="brush: html;"><img src="http://upload.wikimedia.org/wikipedia/commons/thumb/3/31/Photograph_of_1936_of_a_Cabin_Behind_the_Amoureaux_House_in_Ste_Genevieve_MO.jpg/320px-Photograph_of_1936_of_a_Cabin_Behind_the_Amoureaux_House_in_Ste_Genevieve_MO.jpg" alt="" />
</pre>
<br />
This will load the smaller size of the image into the page. Next we add some css to change the image url. Note you may not need so many media queries, depending on the resolutions and sizes you wish to support. I've included the main sizes here:<br />
<br />
<pre class="brush: css;">/* 48em = 768px */
@media (min-width: 48em) {
img {
content:url("http://upload.wikimedia.org/wikipedia/commons/thumb/3/31/Photograph_of_1936_of_a_Cabin_Behind_the_Amoureaux_House_in_Ste_Genevieve_MO.jpg/640px-Photograph_of_1936_of_a_Cabin_Behind_the_Amoureaux_House_in_Ste_Genevieve_MO.jpg");
}
}
/* 64em = 1024px */
@media (min-width: 64em) {
img {
content:url("http://upload.wikimedia.org/wikipedia/commons/thumb/3/31/Photograph_of_1936_of_a_Cabin_Behind_the_Amoureaux_House_in_Ste_Genevieve_MO.jpg/1024px-Photograph_of_1936_of_a_Cabin_Behind_the_Amoureaux_House_in_Ste_Genevieve_MO.jpg");
}
}
/* 80em = 1280px */
@media (min-width: 80em) {
img {
content:url("http://upload.wikimedia.org/wikipedia/commons/thumb/3/31/Photograph_of_1936_of_a_Cabin_Behind_the_Amoureaux_House_in_Ste_Genevieve_MO.jpg/1280px-Photograph_of_1936_of_a_Cabin_Behind_the_Amoureaux_House_in_Ste_Genevieve_MO.jpg");
}
}
</pre>
<br />
If you wish to support screens of different pixel densities you can optionally add more media queries:<br />
<br />
<pre class="brush: css;">@media
only screen and (-webkit-min-device-pixel-ratio: 2) and (min-width: 0em),
only screen and ( min--moz-device-pixel-ratio: 2) and (min-width: 0em),
only screen and ( -o-min-device-pixel-ratio: 2/1) and (min-width: 0em),
only screen and ( min-device-pixel-ratio: 2) and (min-width: 0em),
only screen and ( min-resolution: 192dpi) and (min-width: 0em),
only screen and ( min-resolution: 2dppx) and (min-width: 0em) {
img {
content:url("http://upload.wikimedia.org/wikipedia/commons/thumb/3/31/Photograph_of_1936_of_a_Cabin_Behind_the_Amoureaux_House_in_Ste_Genevieve_MO.jpg/640px-Photograph_of_1936_of_a_Cabin_Behind_the_Amoureaux_House_in_Ste_Genevieve_MO.jpg");
}
}
</pre>
<br />
All browsers that support media queries + css content:url will work with this method including: IE9+, Firefox, Safari and Chrome. The only disadvantage is that the low quality is always loaded by default. Unless you move the img src to a media query.<br />
<br />
You can see a full working example here:<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="300" src="http://jsfiddle.net/kmturley/t1k5rs18/2/embedded/result%2Chtml%2Ccss/" width="100%"></iframe>
And here is another version with extra rules to support retina screens:<br />
<a href="http://jsfiddle.net/kmturley/t1k5rs18/2/">http://jsfiddle.net/kmturley/t1k5rs18/1/</a></div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-48095652386047379822014-11-19T12:47:00.003-08:002014-11-25T06:56:23.171-08:00Create a simple site using php and json<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="p1">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGnBHBi3wAAea_xIQR5u81fm6SpM5r4pf0OEIJjH6VorLwwoEw1Zl75Xow-p216G6A21xBNCbyGIxgilC_H0YSHL9b2km-bqOVNILk8oiLBNIdlmi0-Gg9T3p_Da7N2rn8-xNMXRJZ-G0/s1600/Screen+Shot+2014-11-19+at+3.50.02+PM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGnBHBi3wAAea_xIQR5u81fm6SpM5r4pf0OEIJjH6VorLwwoEw1Zl75Xow-p216G6A21xBNCbyGIxgilC_H0YSHL9b2km-bqOVNILk8oiLBNIdlmi0-Gg9T3p_Da7N2rn8-xNMXRJZ-G0/s1600/Screen+Shot+2014-11-19+at+3.50.02+PM.png" height="109" width="200" /></a></div>
There are many solutions to create a site using out-of-the-box software such as WordPress or Django. However sometimes we just need a very simple custom model such as a portfolio list, or some events. You may want to update your data, but have the paging and templates update automatically.</div>
<div class="p2">
<br /></div>
<div class="p1">
Here is a simple solution which allows you to use PHP to load json files (as data) and then render page templates using TWIG template engine. This solution is fully compatible to then run the json feeds from external sources or APIs at a later date!<br />
<br />
<a name='more'></a></div>
<div class="p2">
First off you need to install PHP on your server. Many will come with it automatically installed, if not head over to <a href="http://www.php.org/">http://www.php.org</a> to download and install php locally. Once installed you should be able to view a php page on your server without errors e.g.</div>
<div class="p2">
<br /></div>
<pre class="“brush:php”"><?php
phpinfo();
?>
</pre>
<div class="p2">
<br /></div>
<div class="p1">
Next we need to create a site folder and install TWIG template library. This will allow us to use a nice format to write templates, and keep a clean separation between controller code in php and views code in TWIG syntax. Download TWIG from <a href="http://twig.sensiolabs.org/">http://twig.sensiolabs.org/</a> and put the folder at:</div>
<div class="p2">
<br /></div>
<pre class="“brush:php”">/php/Twig/
</pre>
<div class="p2">
<br /></div>
<div class="p1">
Next we need to add a custom filter to load JSON files inside the templates. Add a /php/Twig_JSON.php file and paste in the json loading functions:</div>
<div class="p2">
<br /></div>
<pre class="“brush:php”"><?php
class Twig_JSON extends Twig_Extension {
public function getName() {
return 'Twig_JSON';
}
public function getFunctions() {
return array(
'json' => new Twig_Function_Method($this, 'json'),
);
}
public function json($url) {
return json_decode(file_get_contents($url), true);
}
}
?>
</pre>
<div class="p2">
<br /></div>
<div class="p1">
Now we need to create a /php/Main.php file which will load the TWIG template library, the custom TWIG filter we just created, and map browser urls to specific template files:</div>
<div class="p2">
<br /></div>
<pre class="“brush:php”"><?php
require_once 'Twig/Autoloader.php';
require_once 'Twig/ExtensionInterface.php';
require_once 'Twig/Extension.php';
require_once 'Twig_JSON.php';
class Main {
public function init() {
Twig_Autoloader::register();
$this->twig = new Twig_Environment(new Twig_Loader_Filesystem('../html'));
$this->twig->addExtension(new Twig_JSON());
}
public function render() {
$self = explode('/', $_SERVER['PHP_SELF']);
$uri = explode('/', $_SERVER['REQUEST_URI']);
$sections = array_splice($uri, count($self)-2, 1);
if ($sections[0]) {
return $this->twig->render($sections[0].'.html');
} else {
return 'Try a page url e.g. <a href="./home">home</a>';
}
}
}
$main = new Main();
$main->init();
echo $main->render();
?>
</pre>
<div class="p2">
<br /></div>
<div class="p1">
I’ve also added an optional index.php in the root to direct the request to the default template: home. This is because root url doesn’t have a section, so I don’t have a template which will be loaded. By redirecting the root to point to home, i’m telling it to render the home template:</div>
<div class="p2">
<br /></div>
<pre class="“brush:php”"><?php
header("Location: home");
exit;
?>
</pre>
<div class="p2">
<br /></div>
<div class="p1">
As part of my configuration i’ve also decided to add an .htaccess file at the root of the site to ensure all directory and folder requests hit our /php/Main.php file instead of real folders. This is optional depending on your setup but I think works well for mapping urls to templates:</div>
<div class="p2">
<br /></div>
<pre class="“brush:php”">RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . php/Main.php [L]
</pre>
<div class="p2">
<br /></div>
<div class="p1">
Now we need some data! Lets create a json file at /json/navigation.json with some test data:</div>
<div class="p2">
<br /></div>
<pre class="“brush:js”">[
{
"title": "Home",
"url": "./home"
},
{
"title": "Videos",
"url": "./videos"
}
]
</pre>
<div class="p2">
<br /></div>
<div class="p1">
And finally some templates! we can now load the json file using a TWIG filter and output it into the template. Lets create a template at /templates/home.html with the content:</div>
<div class="p2">
<br /></div>
<pre class="“brush:html”"><!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Json templates</title>
</head>
<body>
<h2>navigation</h2>
<ul>
{% set items = json('../json/navigation.json') %}
{% for item in items %}
<li><a href="{{ item.url }}">{{ item.title }}</a></li>
{% endfor %}
</ul>
</body>
</html>
</pre>
<div class="p1">
<br />
The json() filter allows you to pass through the url to the json file, which loads and returns it back to the template. This can then be counted/looped through and outputted. A very easy way to keep separation between view templates and your data sets.<br />
<br />
You can download a working example here:<br />
<a href="https://github.com/kmturley/json-templates">https://github.com/kmturley/json-templates</a></div>
</div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-82696374268564171122014-11-03T13:38:00.000-08:002015-04-07T07:03:02.115-07:00Circular loading bar using css only<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpKC7SB_7Rrop2bn90TT8EnZNmxRZVdAj7Syozh3DyAxIjcxo_iK1BmJzR81avHnP8N9zkfJve6fbmZOu6N748mVIqHTLK61scQsn3SJSOnc0Go-CPs7Qqzr8Q8cfWRls_D4P_LQycueo/s1600/bird.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="136" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpKC7SB_7Rrop2bn90TT8EnZNmxRZVdAj7Syozh3DyAxIjcxo_iK1BmJzR81avHnP8N9zkfJve6fbmZOu6N748mVIqHTLK61scQsn3SJSOnc0Go-CPs7Qqzr8Q8cfWRls_D4P_LQycueo/s200/bird.jpg" width="200" /></a></div>
I've recently started a new project where audio files are played in loops. This presents an interesting challenge as the loading bar makes more sense to be circular rather than linear. Added to this I also wanted to be able to control the amount of time it takes for the animation to complete, and to be able to loop it. Here is how I achieved it!<br />
<a name='more'></a>First up is to create a container which keeps the rotating circles together with the circles inside:<br />
<br /></div>
<pre class="brush: html"><div class="loader">
<div class="left"><span></span></div>
<div class="right"><span></span></div>
</div>
</pre>
<br />
Next is to add some css styling to this container:<br />
<br />
<pre class="brush: css">.loader {
display: inline-block;
position: relative;
}
</pre>
<br />
The display-inline block is to allow the container to be centered on the page, and position relative is to ensure the elements inside are kept together. Next up is the styling for the wrappers:<br />
<br />
<pre class="brush: css">.left,
.right {
margin-top: -50px;
margin-left: -50px;
position: absolute;
clip: rect(0, 100px, 100px, 50px);
}
.right {
clip: rect(0px, 50px, 100px, 0px);
}
</pre>
<br />
Both left and right wrappers are positioned and aligned to their center (half their width). Additionally they are clipped, which hides some of their content. The reason behind this is to make the circles inside half-circles rather than full. Inside the wrappers we can create the circles:<br />
<br />
<pre class="brush: css">.left span,
.right span {
width: 80px;
height: 80px;
border-radius: 100%;
position: absolute;
opacity: 0.7;
border: 10px solid #fff;
}
</pre>
<br />
This sets the circle sizes and uses border-radius to shape the circles. This method has an extra benefit which means the circles can have opacity, or even an empty space inside like a doughnut. Now all we need are the animations to rotate the circles at the correct times:<br />
<br />
<pre class="brush: css">.left span {
clip: rect(0px, 50px, 100px, 0px);
-webkit-animation: rotate-left 5s infinite linear;
-moz-animation: rotate-left 5s infinite linear;
-ms-animation: rotate-left 5s infinite linear;
-o-animation: rotate-left 5s infinite linear;
animation: rotate-left 5s infinite linear;
}
.right span {
clip: rect(0, 100px, 100px, 50px);
-webkit-animation: rotate-right 5s infinite linear;
-moz-animation: rotate-right 5s infinite linear;
-ms-animation: rotate-right 5s infinite linear;
-o-animation: rotate-right 5s infinite linear;
animation: rotate-right 5s infinite linear;
}
</pre>
<br />
Each side has a different clipping mask and a different animation to ensure the smooth loop between left and right sides. Finally the actual css animation code:<br />
<br />
<pre class="brush: css">/* rotate-left */
@-webkit-keyframes rotate-left {
0% { -webkit-transform: rotate(0deg); }
50% { -webkit-transform: rotate(180deg); }
100% { -webkit-transform: rotate(180deg); }
}
@-moz-keyframes rotate-left {
0% { -moz-transform: rotate(0deg); }
50% { -moz-transform: rotate(180deg); }
100% { -moz-transform: rotate(180deg); }
}
@-ms-keyframes rotate-left {
0% { -ms-transform: rotate(0deg); }
50% { -ms-transform: rotate(180deg); }
100% { -ms-transform: rotate(180deg); }
}
@-o-keyframes rotate-left {
0% { -o-transform: rotate(0deg); }
50% { -o-transform: rotate(180deg); }
100% { -o-transform: rotate(180deg); }
}
@keyframes rotate-left {
0% { transform: rotate(0deg); }
50% { transform: rotate(180deg); }
100% { transform: rotate(180deg); }
}
/* rotate-right */
@-webkit-keyframes rotate-right {
0% { -webkit-transform: rotate(0deg); }
50% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(180deg); }
}
@-moz-keyframes rotate-right {
0% { -moz-transform: rotate(0deg); }
50% { -moz-transform: rotate(0deg); }
100% { -moz-transform: rotate(180deg); }
}
@-ms-keyframes rotate-right {
0% { -ms-transform: rotate(0deg); }
50% { -ms-transform: rotate(0deg); }
100% { -ms-transform: rotate(180deg); }
}
@-o-keyframes rotate-right {
0% { -o-transform: rotate(0deg); }
50% { -o-transform: rotate(0deg); }
100% { -o-transform: rotate(180deg); }
}
@keyframes rotate-right {
0% { transform: rotate(0deg); }
50% { transform: rotate(0deg); }
100% { transform: rotate(180deg); }
}
</pre>
<br />
And that's it...a working radial animation in Firefox, Chrome, Safari and modern IE browsers. In my demo I've also decided to center the loader vertically and horizontally using table-cells. If you would like to change the animation loop time using JavaScript it's as a simple as using:<br />
<br />
<pre class="brush: js">circleLeft.style['-webkit-animation-duration'] = '12.5s';
circleRight.style['-webkit-animation-duration'] = '12.5s';
</pre>
<br />
You can view the working version here:<br />
<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="300" src="http://jsfiddle.net/kmturley/54hqzmk7/2/embedded/result,html,css" width="100%"></iframe>
<a href="http://jsfiddle.net/kmturley/54hqzmk7/2/">http://jsfiddle.net/kmturley/54hqzmk7/2/</a><br />
<br /></div>Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-63845652334022138462014-10-03T11:37:00.001-07:002015-05-19T08:16:27.853-07:00Facebook login inside a hybrid app using Cordova and OpenFB<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhwWfYKkL4CCqHC4zjlZFeYzhZUogbqJX6hHya6kbnPZJGwpuZKqet76iwDsus3ddNoCMAGeu2eh-1_Y8RVEUS2aUEf8FgMNpmJyZy7zKg7wvfUr0D8qF_meZHDHEXQzf84qr_mJvCs0E/s1600/mobile.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhwWfYKkL4CCqHC4zjlZFeYzhZUogbqJX6hHya6kbnPZJGwpuZKqet76iwDsus3ddNoCMAGeu2eh-1_Y8RVEUS2aUEf8FgMNpmJyZy7zKg7wvfUr0D8qF_meZHDHEXQzf84qr_mJvCs0E/s200/mobile.jpg" width="119" /></a></div>
Recently i've been playing with <a href="http://cordova.apache.org/" target="_blank">Apache Cordova</a> more and more. It opens up a new set of API's, allowing you to use native features of a device from within a web view using JavaScript.<br />
<br />
However one issue i've come across is trying to get Facebook Login to work correctly. Here I will explain the different ways to implement Facebook login with a Hybrid app and which method worked well for me.<br />
<br />
<a name='more'></a><br />
If you try to use the standard <a href="https://developers.facebook.com/docs/javascript" target="_blank">Facebook JavaScript SDK</a> you will encounter two errors. One loading the sdk over file:// means it doesn't load. The solution to this is to change the line:<br />
<br />
<pre class="brush: js"> js.src = "//connect.facebook.net/en_US/sdk.js";
</pre>
<br />
To the following:<br />
<br />
<div>
<pre class="brush: js"> js.src = "http://connect.facebook.net/en_US/sdk.js";
</pre>
</div>
<br />
However after the SDK loads, you get a second message preventing you accessing the API from a local file:// url.<br />
<br />
A second potential solution is to use the <a href="https://github.com/Wizcorp/phonegap-facebook-plugin" target="_blank">Cordova Facebook Plugin</a> which uses the Facebook native SDKs to bridge Cordova and the Facebook API. However I encountered many issues trying to get the Cordova project to detect the Facebook native SDK's which required going into Eclipse and Xcode to manage project assets and libraries. This was a real pain and I ended up not even being able to build a working app as easily as guides suggested.<br />
<br />
Then I stumbled across a third solution which is working really well for me, and even better, no native code libraries! The trick here is to bypass the Facebook JavaScript SDK library and use the Facebook REST api endpoints directly. I am using <a href="https://github.com/ccoenraets/OpenFB" target="_blank">OpenFB javascript library</a> to make this job easier, rather than write the calls all myself, but you can do it either way. I've outlined the steps on how to get it to work below.<br />
<br />
1) Create a Facebook app and update the URL settings under Basic and Advanced to allow callbacks using facebook or your local urls. Then copy the Facebook ID ready for the next step<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5AkjQCcoyDdvCnRhWVKKWJDHJhc7w4TdMzIL4GJ7Mu8eTEIIsd-q_YMUleKWU_qDDRH8CXMABwq5Lyhm1wjY_lbRRsXXfbI9fNYNcTsRYHKBQSaLyE2YgehIpuvlej-fBKK0q9ZLW-S8/s1600/Screen+Shot+2014-10-03+at+9.56.47+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="182" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5AkjQCcoyDdvCnRhWVKKWJDHJhc7w4TdMzIL4GJ7Mu8eTEIIsd-q_YMUleKWU_qDDRH8CXMABwq5Lyhm1wjY_lbRRsXXfbI9fNYNcTsRYHKBQSaLyE2YgehIpuvlej-fBKK0q9ZLW-S8/s1600/Screen+Shot+2014-10-03+at+9.56.47+AM.png" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcYxhrTQ7D6dITh-uolSkZ0Csv2TztBhwZyJnA6KvcutsvXNQiuPSgiXcJuadYTTV2qkE5wPB3wYZVlCavAsi2F2Xoj6j1WbiT8IYm_sGCVazRsrRHADM8SKxEDGC8q0i6fyU4NEXC3f8/s1600/Screen+Shot+2014-10-03+at+9.57.10+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="171" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcYxhrTQ7D6dITh-uolSkZ0Csv2TztBhwZyJnA6KvcutsvXNQiuPSgiXcJuadYTTV2qkE5wPB3wYZVlCavAsi2F2Xoj6j1WbiT8IYm_sGCVazRsrRHADM8SKxEDGC8q0i6fyU4NEXC3f8/s1600/Screen+Shot+2014-10-03+at+9.57.10+AM.png" width="640" /></a></div>
<br />
2) If you haven't already, download and install <a href="http://cordova.apache.org/" target="_blank">Cordova</a> to put the tools in your command line. Then navigate to your Sites folder and run the following commands to create the project and add your platforms:<br />
<br />
<div style="-webkit-text-stroke-width: 0px; color: black; font-family: Times; font-size: medium; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: auto; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px;">
<pre class="brush: js"> cordova create your-project-name
cordova platform add ios
cordova platform add android
</pre>
<div>
</div>
</div>
<br />
3) Now we need to add a Cordova plugin to handle pop-up windows from facebook logins. To add a plugin use the command:<br />
<br />
<div>
<pre class="brush: js"> cordova plugin add org.apache.cordova.inappbrowser
</pre>
<div>
</div>
</div>
<br />
4) Now we just need to download and configure <a href="https://github.com/ccoenraets/OpenFB" target="_blank">OpenFB</a> inside our new Cordova project. For this example we will just use the test page they provide, so download it from the <a href="https://github.com/ccoenraets/OpenFB" target="_blank">OpenFB Github page</a> and extract the files into your cordova project /www/ folder. After this open the index.html and edit the following line with your Facebook App ID from step 1:<br />
<br />
<div>
<pre class="brush: js"> openFB.init({appId: 'YOUR_FB_APP_ID'});
</pre>
<div>
</div>
</div>
<br />
5) You should now be able to run the example and login using your local browser setup. I had an issues at this point, which was that the console.log was logging out:<br />
<br />
<div>
<pre class="brush: js"> http://localhost:8888/cordova-open-fb/oauthcallback.html
http://localhost:8888/cordova-open-fb/www/logoutcallback.html
</pre>
<div>
</div>
</div>
<br />
Which looks like a bug as the folder path should be:<br />
<br />
<div>
<pre class="brush: js"> http://localhost:8888/cordova-open-fb/www/oauthcallback.html
http://localhost:8888/cordova-open-fb/www/logoutcallback.html
</pre>
<div>
<span style="color: #444444; font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;"><span style="font-size: 14px;"><br /></span></span></div>
<div>
The fix I came up with was to modify openfb.js line 20 from:</div>
<div>
<pre class="brush: js"> context = window.location.pathname.substring(0, window.location.pathname.indexOf("/",2)),
</pre>
</div>
<div>
<span style="color: #444444; font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;"><span style="font-size: 14px;"><br /></span></span></div>
<div>
To be:</div>
<div>
<pre class="brush: js"> context = window.location.pathname.substring(0, window.location.pathname.lastIndexOf("/")),
</pre>
</div>
<div>
<span style="color: #444444; font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;"><span style="font-size: 14px;"><br /></span></span></div>
</div>
<div>
6) To test on iOS simulator you will need <a href="https://developer.apple.com/xcode/" target="_blank">xCode</a> installed then run the command:<br />
<br />
<div>
<pre class="brush: js"> cordova emulate ios
</pre>
<div>
</div>
</div>
<div>
<br /></div>
To test on an android emulator you will need the <a href="https://developer.apple.com/xcode/" target="_blank">Android SDK</a> installed and then run the command:<br />
<br />
<pre class="brush: js"> cordova emulate android
</pre>
<div>
<span style="color: #444444; font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;"><span style="font-size: 14px;"><br /></span></span></div>
To test on an iOS device connected with a cable, run following command: </div>
<div>
<br />
<pre class="brush: js"> cordova run ios
</pre>
<div>
<span style="color: #444444; font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;"><span style="font-size: 14px;"><br /></span></span></div>
<div>
To test on an Android device connected with a cable, run following command: </div>
<div>
<br />
<pre class="brush: js"> cordova run android
</pre>
<div>
<br /></div>
</div>
<div>
<span style="color: #444444; font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;"><span style="font-size: 14px;"><br /></span></span></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqUVpjOHBSfGZfNRfq7vhDInURxgf2ZDrk52P7RCeW4bCOobndl8cJZYB3W4HSsn5UTVTImd6ZfLYis00a4N25bJtAXtOU-6bJ3fPG_TUHyatdA-2M5aIvUGepJ6i57ADIHFnB-Omc5d8/s1600/Screen+Shot+2014-10-03+at+9.38.05+AM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqUVpjOHBSfGZfNRfq7vhDInURxgf2ZDrk52P7RCeW4bCOobndl8cJZYB3W4HSsn5UTVTImd6ZfLYis00a4N25bJtAXtOU-6bJ3fPG_TUHyatdA-2M5aIvUGepJ6i57ADIHFnB-Omc5d8/s1600/Screen+Shot+2014-10-03+at+9.38.05+AM.png" width="286" /></a></div>
<div>
<span style="color: #444444; font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;"><span style="font-size: 14px;"><br /></span></span></div>
<div>
<span style="color: #444444; font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;"><span style="font-size: 14px;"><br /></span></span></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9qb26JWS8DNb_TjBDeypO2twqq6_8-t-R3hUlnMq-R061Xazy_R1P9mVzr1pg5fZtPJQZ788ZaP6UDK7sf8dy2Br57zxZUoag4CPFs6FKReUnyv17Ky3BqyugkgqS_Kg9riFpTy1RZZM/s1600/Screen+Shot+2014-10-03+at+9.38.21+AM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9qb26JWS8DNb_TjBDeypO2twqq6_8-t-R3hUlnMq-R061Xazy_R1P9mVzr1pg5fZtPJQZ788ZaP6UDK7sf8dy2Br57zxZUoag4CPFs6FKReUnyv17Ky3BqyugkgqS_Kg9riFpTy1RZZM/s1600/Screen+Shot+2014-10-03+at+9.38.21+AM.png" width="202" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7MY-eYOpl26jAMGcU6xHkf4Q4NvejNhbiZDK9g4WskDmuRWsvx1cE8HjytxmSXtC-JRdQTw3ezpFAfmni_x4M32mlIuKPVMkBRgyXontyzO9LmNklDD_02-JodL7F89gE20q55mHfQ40/s1600/Screen+Shot+2014-10-03+at+9.38.45+AM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7MY-eYOpl26jAMGcU6xHkf4Q4NvejNhbiZDK9g4WskDmuRWsvx1cE8HjytxmSXtC-JRdQTw3ezpFAfmni_x4M32mlIuKPVMkBRgyXontyzO9LmNklDD_02-JodL7F89gE20q55mHfQ40/s1600/Screen+Shot+2014-10-03+at+9.38.45+AM.png" width="238" /></a></div>
<div>
<span style="color: #444444; font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;"><span style="font-size: 14px;"><br /></span></span></div>
<div>
<span style="color: #444444; font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;"><span style="font-size: 14px;"><br /></span></span></div>
<div>
<span style="color: #444444; font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;"><span style="font-size: 14px;"><br /></span></span></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
Here is a full working example in code:</div>
<div>
<a href="https://github.com/kmturley/cordova-open-fb">https://github.com/kmturley/cordova-open-fb</a></div>
<div>
<br />
Here is a video of the app working on desktop and iPad:<br />
<br /></div>
<iframe allowfullscreen="" frameborder="0" height="500" src="https://www.youtube.com/embed/rVtHjJA_h_Y" width="100%"></iframe>
</div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-11718000139478050692014-08-19T09:22:00.001-07:002014-11-25T12:33:48.636-08:00Improving the AngularJS app starter template<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiI4rIMcKM1l365YdAv7Mg81sSlfcJGUT-Ljgpe86af96xXR6bEKbOKJ6DAa83rI0jjh1yCrhLa-X3SFuS1sD4Uf0Kl5mmZ6iPJZvwJ0aTtXRazlLFv0Dcc2IzIhaIJ9zUGemUuTxa8LIs/s1600/dashboard.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="105" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiI4rIMcKM1l365YdAv7Mg81sSlfcJGUT-Ljgpe86af96xXR6bEKbOKJ6DAa83rI0jjh1yCrhLa-X3SFuS1sD4Uf0Kl5mmZ6iPJZvwJ0aTtXRazlLFv0Dcc2IzIhaIJ9zUGemUuTxa8LIs/s200/dashboard.jpg" width="200" /></a></div>
AngularJS is a very powerful front-end library which allows you to build apps and prototypes quickly, without getting bogged down in browser inconsistencies. However when you look at the documentation and examples you will find that they vary massively and there are several ways to write, organise and build an app.<br />
<br />
I've built a few projects using AngularJS & Twitter Bootstrap, and have now settled on a structure which feels easy to maintain and understand. I will attempt to explain it here with examples.<br />
<br />
<a name='more'></a><br />
The main requirements of my starter app are:<br />
<br />
<ul style="text-align: left;">
<li>Setup Guide, how to get started etc</li>
<li>Libraries, AngularJS for functionality & Twitter Bootstrap for grid/layout/styling</li>
<li>File & Folder Structure, grouped by functionality into modules</li>
<li>Example App, showing common features: load list, filter, sort, load single item, edit, save</li>
<li>Task Runner, automate common processes</li>
<li>Documentation, auto generated from the code</li>
<li>Automated Tests, for the future</li>
<li>Fully responsive</li>
<li>Style guide</li>
</ul>
<div>
And hopefully it will look something like this!</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZA33sF456pBBDBOwr6l6scQ8lYhwXU379sWcetYWdeGsHXovKmIHD34CUgRQFE20glN5JYYKmbxobResPrxauX7egSCxHbKOeEfdaVc-IAlBm-deWlvzSU8cRkX17kGytcrdJ0Dpr9EI/s1600/Screen+Shot+2014-08-19+at+12.19.51+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZA33sF456pBBDBOwr6l6scQ8lYhwXU379sWcetYWdeGsHXovKmIHD34CUgRQFE20glN5JYYKmbxobResPrxauX7egSCxHbKOeEfdaVc-IAlBm-deWlvzSU8cRkX17kGytcrdJ0Dpr9EI/s1600/Screen+Shot+2014-08-19+at+12.19.51+PM.png" height="336" width="640" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<b><br /></b></div>
<div>
<b><br /></b></div>
<div>
<b><br /></b></div>
<div>
<b><br /></b></div>
<div>
<b><br /></b></div>
<div>
<b><br /></b></div>
<div>
<b><br /></b></div>
<div>
<b><br /></b></div>
<div>
<b><br /></b></div>
<div>
<b><br /></b></div>
<div>
<b>Setup guide</b></div>
<div>
A README.md file in the root of the site which is compatible with GitHub's readme format. It explains the technologies used, how to install and run the commands, the file/folder structure and the contact information for support. I've included some lines of the readme here as an example:</div>
<div>
<br /></div>
<div>
<pre class="brush:html">angular-bootstrap
===========
AngularJS and Twitter bootstrap combined
AngularJS `http://angularjs.org/`
Twitter bootstrap `http://getbootstrap.com/`
## Installation and running tasks
Install [node.js](http://nodejs.org/) then navigate to the site root within terminal and type:
npm install
Once the node modules have installed you should now have access to run the task runner. In your terminal you should be able to use the following commands:
gulp docs
gulp test
gulp watch</pre>
<div>
</div>
</div>
<div>
<br /></div>
<div>
<b>Libraries</b></div>
<div>
For the example we need to include the libraries the example is based on. My example uses the following libraries:</div>
<div>
<ul style="text-align: left;">
<li>angular.min.js - the core angular library (107KB)</li>
<li>angular-resource.min.js - optional resource library for requests (3KB)</li>
<li>angular-ui-bootstrap.min.js - angular ui for twitter bootstrap components (64KB) </li>
<li>angular-ui-router.min.js - optional router for nested views and other things (20KB)</li>
<li>bootstrap.min.css - and the twitter bootstrap css library (110KB)</li>
<li>glyphicons-halflings-regular.ttf - the vector scalable font icons (40KB)</li>
</ul>
</div>
<div>
<b>File & Folder Structure</b></div>
<div>
The file and folder structure is important to get right from the start. This prevents developers getting confused and putting the wrong code in the wrong places, which eventually reduces efficiency. Another key part to the structure is organising the modules by functionality (item, list, overlay) instead of type (css, js, html)</div>
<div>
<br /></div>
<div>
<div>
<pre class="brush:html">app/ --> all of the files to be used in production
data/ --> json data files
index.html --> app layout file (the main html template file of the app)
libs/ --> external libraries and fonts
modules/ --> modules grouped by functionality
app/ --> main application module
app.css
app.html
app.js
item/ --> view/edit item
item.css
item.html
item.js
item-new/ --> new item
item-new.css
item-new.html
item-new.js
items/ --> list of items
items.css
items.html
items.js
overlay/ --> popup overlay
overlay.css
overlay.html
overlay.js
gulpfile.js --> task runner settings
package.json --> node.js module settings
README.md --> Setup guide
test/ --> test source files and libraries
e2e/ --> end-to-end test runner (open in your browser to run)
unit/ --> unit level specs/tests</pre>
<div>
</div>
</div>
</div>
<div>
<br /></div>
<div>
<b>Example App</b></div>
<div>
For this to all work we need an example app which shows off this structure. As part of the app i've added some common features needed including:</div>
<div>
<ul style="text-align: left;">
<li>Login</li>
<li>List of items (sortable, filtered, custom date ranges, pagination)</li>
<li>Single item view (requests REST api, tabbed view with view and edit states)</li>
<li>New item form (filling out data in steps, datepicker, submit to server)</li>
<li>User profile in overlay</li>
<li>Custom timecode inputs (testing different ways of entering/displaying data)</li>
</ul>
<div>
<div>
You can view this all working here:</div>
<div>
<a href="http://kmturley.github.io/angular-bootstrap/app/">http://kmturley.github.io/angular-bootstrap/app/</a></div>
</div>
</div>
<div>
<br /></div>
<div>
<b>Task Runner</b></div>
<div>
For the task runner i'm using Node.js with Gulp to watch and run tasks via the command line. All the settings are contained within the gulpfile.js which includes:</div>
<div>
<ul style="text-align: left;">
<li>gulp test - run automated test using karma and jasmine</li>
<li>gulp docs - auto generate the documentation form the code using jsdoc</li>
<li>gulp watch - watch all javascript files for changes, when a change occurs it runs gulp test</li>
</ul>
</div>
<div>
<b>Documentation</b></div>
<div>
After running the gulp docs command, the documentation is generated into a docs folder in the root of the site. This can then be viewed using any web browser. The documentation plugin used is called jsdoc. Which requires comments to be written in the following format:</div>
<div>
<br /></div>
<div>
<pre class="brush:js">/**
* @file Item
* @summary Item module
*/
/*globals window, angular, document */</pre>
</div>
<div>
</div>
<div>
<br /></div>
<div>
<b>Automated Tests</b></div>
<div>
After running the gulp test command it will scan any tests in the test folder and output results into the command line. Here is an example of a test for an angular module:</div>
<div>
<br /></div>
<div>
<pre class="brush:js">/**
* Test
**/
/*globals jasmine, describe, beforeEach, afterEach, module, it, inject, expect */
describe('Controllers.Submissions', function () {
'use strict';
beforeEach(function () {
module('Services.Login');
module('Services.Submission');
module('Services.URL');
module('Controllers.Submissions');
});
describe('SubCtrl', function () {
var scope,
controller,
http;
beforeEach(inject(function ($rootScope, $controller, $injector) {
$rootScope.url = '/';
$rootScope.urlExt = '';
scope = $rootScope.$new();
controller = $controller('SubCtrl', {$scope: scope});
http = $injector.get('$httpBackend');
}));
afterEach(function () {
http.verifyNoOutstandingExpectation();
http.verifyNoOutstandingRequest();
});
it('should have fetchSubData', function () {
expect(scope.fetchSubData).toBeDefined();
expect(scope.fetchSubData.loadingInd).toBeDefined();
expect(scope.fetchSubData.loadingInd).toEqual(false);
});
it('should have getUsers', function () {
var feed = { response: 'example' };
expect(scope.getUsers).toBeDefined();
http.expectGET('/submissionusers?searchstring=joe');
http.whenGET('/submissionusers?searchstring=joe').respond(feed);
scope.getUsers('joe').then(function (data) {
expect(data).toEqual(feed);
});
http.flush();
});
});
});</pre>
</div>
<div>
</div>
<div>
<br /></div>
<div>
<b>Fully Responsive</b></div>
<div>
For this feature we are using Twitter Bootstrap which makes it extremely easy to create a grid that responds to any browser/screen size. You can also re-order rows and columns to keep inportant information at the top of the screen. In the example app we have a sidebar which jumps to the top when on mobile:</div>
<div>
<br /></div>
<div>
<pre class="brush:html"><div class="row">
<div class="col-md-4 col-md-push-8">
<p>This is the sidebar on desktop, but at the top on mobile</p>
</div>
<div class="col-md-8 col-md-pull-4">
<p>This is the main list of items</p>
</div>
</div></pre>
<div>
</div>
</div>
<div>
<br /></div>
<div>
We could write this part ourselves, but as we are using the form, dropdowns and input styling from bootstrap it makes sense to embrace it fully!<br />
<br />
<div>
<b>Style Guide</b></div>
<div>
The last feature for our web app is a dynamic style guide, which contains all the elements used across the app. This page is useful to check consistency of design across UI elements and ensure there are no bugs when put together. For my style guide i've used a Twitter Bootstrap template and modified it to include Angular and some custom elements here:<br />
<a href="http://kmturley.github.io/angular-bootstrap/app/style-guide.html">http://kmturley.github.io/angular-bootstrap/app/style-guide.html</a></div>
</div>
<div>
<br /></div>
<div>
So there we have it, a fully working app built using AngularJS and Twitter Bootstrap, structured into modules, with documentation, automated tests and a guide.</div>
<div>
<br /></div>
<div>
You can view the source project here:</div>
<div>
<a href="https://github.com/kmturley/angular-bootstrap">https://github.com/kmturley/angular-bootstrap</a></div>
<div>
<br /></div>
<div>
And view the working app here:</div>
<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="300" src="http://kmturley.github.io/angular-bootstrap/app/" width="100%"></iframe>
<br />
<div>
<a href="http://kmturley.github.io/angular-bootstrap/app/">http://kmturley.github.io/angular-bootstrap/app/</a></div>
</div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-37772110177168736562014-07-10T12:31:00.002-07:002016-12-02T19:32:37.715-08:00Responsive cross browser lightbox using only html and css<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3q8DIvEswHqZjVlQtdRtrilmpuAWWcF1l0s2PP9Oq7hXTYOXm-9eCCU9S_VyzY0EMHMV4b_G0P4g6k2WKKJGbY5e09xUPK1K1TVTsnTMws5Dustqams6KtjeGWkr_Zx1PjHxoK7MnfC8/s1600/lightbox.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="142" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3q8DIvEswHqZjVlQtdRtrilmpuAWWcF1l0s2PP9Oq7hXTYOXm-9eCCU9S_VyzY0EMHMV4b_G0P4g6k2WKKJGbY5e09xUPK1K1TVTsnTMws5Dustqams6KtjeGWkr_Zx1PjHxoK7MnfC8/s200/lightbox.jpg" width="200" /></a></div>
There are many great lightbox plugins out there, however lots of them rely on jQuery or other libraries. Also another negative aspect is that they use JavaScript for the resizing and other parts which really isn't needed. So how can we make our own css only efficient lightbox overlay, which works cross browser IE8+?<br />
<br />
<a name='more'></a><br />
Firstly we need the html wrappers for the content. We have four wrappers for the background, scrollable area, centering vertically and centering horizontal.<br />
<br />
<pre class="brush:html"><div class="overlay">
<div class="t">
<div class="tc">
<div class="content">
<h1>This is the lightbox overlay</h1>
<p>Check it at different sizes to see how it works</p>
</div>
</div>
</div>
</div></pre>
<div>
<br /></div>
<div>
Now we add some css to the background outer wrapper, to position it on top and fill the screen with black:</div>
<div>
<br /></div>
<div>
<pre class="brush:css">.overlay {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: 5;
overflow: auto;
background: rgb(76, 76, 76);
background-color: rgba(0, 0, 0, 0.8);
}</pre>
</div>
<div>
<br /></div>
<div>
Then the css added to the inner wrapper allows the content to be aligned vertically and horizontally:</div>
<div>
<br /></div>
<div>
<pre class="brush:css">.overlay .t {
margin: auto;
display: table;
width: 100%;
height: 100%;
}
.overlay .tc {
display: table-cell;
vertical-align: middle;
}</pre>
</div>
<div>
<span style="color: #444444; font-family: "consolas" , "menlo" , "monaco" , "lucida console" , "liberation mono" , "dejavu sans mono" , "bitstream vera sans mono" , "courier new" , monospace , serif;"><span style="font-size: 14px;"><br /></span></span></div>
<div>
<div>
And finally the content wrapper can then restrict the content width and style the lightbox background/padding depending on the content type:</div>
<div>
<br /></div>
<div>
<pre class="brush:css">.overlay .content {
max-width: 680px;
margin: 0 auto;
padding: 20px;
background-color: #fff;
}</pre>
</div>
<br />
<div>
Here's how that looks so far:</div>
<div>
<a href="http://jsfiddle.net/kmturley/m2vEN/2/show/">http://jsfiddle.net/kmturley/m2vEN/2/show/</a></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4of1gLdxgM6XsghnbkXcsEUMh9ITaeH1Av4Xm4gaxNpk5ZxLQs9e0TzmZUsw_2UWXqEbBzBbA6jVvOscosrVVj_X4u-_qi68lIoHDWA24aFKW2Wds-XABuu7sH_WBzH5gIWsbnlnhQ_c/s1600/Screen+Shot+2014-07-10+at+3.26.15+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="433" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4of1gLdxgM6XsghnbkXcsEUMh9ITaeH1Av4Xm4gaxNpk5ZxLQs9e0TzmZUsw_2UWXqEbBzBbA6jVvOscosrVVj_X4u-_qi68lIoHDWA24aFKW2Wds-XABuu7sH_WBzH5gIWsbnlnhQ_c/s1600/Screen+Shot+2014-07-10+at+3.26.15+PM.png" width="640" /></a></div>
<br />
<div>
If you want a different background, rather than a black background you can blur the content behind using the following code:</div>
<div>
<br /></div>
<div>
<pre class="brush:html"><div class="page toblur blur">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vitae quam in enim volutpat pulvinar.</p>
</div></pre>
</div>
<br />
<div>
You then add the css to blur the background. The blur class can be added and removed to trigger the animation effect:</div>
<br />
<pre class="brush:css">.toblur {
-webkit-transform: translate3d(0, 0, 0);
-webkit-filter: blur(0px);
-moz-filter: blur(0px);
-o-filter: blur(0px);
-ms-filter: blur(0px);
filter: blur(0px);
-webkit-transition: all ease-out 0.2s;
-moz-transition: all ease-out 0.2s;
-o-transition: all ease-out 0.2s;
-ms-transition: all ease-out 0.2s;
transition: all ease-out 0.2s;
}
.blur {
-webkit-filter: blur(5px);
-moz-filter: blur(5px);
-o-filter: blur(5px);
-ms-filter: blur(5px);
filter: blur(5px);
}</pre>
<br />
<div>
Here's how that looks now:</div>
<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="300" src="https://jsfiddle.net/kmturley/m2vEN/3/embedded/result,html,css" width="100%"></iframe>
<a href="http://jsfiddle.net/kmturley/m2vEN/3/">http://jsfiddle.net/kmturley/m2vEN/3/</a>
<br />
<br />
If you need margins around the lightbox:<br />
<br />
Flexbox version (IE11 align top as it doesn't support the margin: auto fix) <a href="https://jsfiddle.net/kmturley/570ka9Lq/1/">https://jsfiddle.net/kmturley/570ka9Lq/1/</a><br />
<br />
Tablecell version (Works well in all browsers)<br />
<a href="http://jsfiddle.net/kmturley/m2vEN/12/">http://jsfiddle.net/kmturley/m2vEN/12/</a></div>
</div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-28913168201541756512014-06-16T08:28:00.000-07:002017-03-13T08:43:37.674-07:00Responsive mobile dropdown navigation using css only<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6cnm9hM_YHyBpwfR0nD8ehk1rXiO1q0HGiv465qdtEhr_IBg-xykmJ_c93ME2Jp7z1ST6VRaJFrBtP-2CftbXq5FS4VhJonBAVYX8ig0c_EU5ySV2469mVUmWxaf25i24MwcH1n4f5Po/s1600/Screen-Shot-2014-11-21-at-10.50.54-AM.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="112" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6cnm9hM_YHyBpwfR0nD8ehk1rXiO1q0HGiv465qdtEhr_IBg-xykmJ_c93ME2Jp7z1ST6VRaJFrBtP-2CftbXq5FS4VhJonBAVYX8ig0c_EU5ySV2469mVUmWxaf25i24MwcH1n4f5Po/s1600/Screen-Shot-2014-11-21-at-10.50.54-AM.jpg" width="200" /></a></div>
When building a fully responsive site the layout should adapt to different screen sizes, from mobile to tablet and desktop. Navigations are usually complicated as the space is limited, and functionality can differ between mobile and desktop.<br />
Here we will look at a css only solution to a responsive dropdown navigation!<br />
<a name='more'></a><span style="background-color: white; box-sizing: border-box; color: #333333; font-family: "helvetica neue" , "helvetica" , "arial" , sans-serif; font-size: 14px; font-weight: 700; line-height: 20px;">Updated 12/12/15:</span><span style="background-color: white; color: #333333; font-family: "helvetica neue" , "helvetica" , "arial" , sans-serif; font-size: 14px; line-height: 20px;"> added css icon with transition animation, and using input checkbox for toggling</span><br />
<br />
One common layout for a desktop navigation is to have fixed horizontal items, and then on mobile to have a drop-down using a menu (hotdog/hamburger) icon. Most examples use JavaScript to achieve this, but there is a way using css.<br />
<br />
First you will need to create a list containing the main navigation items. Then add an checkbox input/label element. This will become our main navigation button using a span as the <span style="background-color: white; color: #222222; font-family: "menlo" , monospace; font-size: 11px; white-space: pre-wrap;">☰</span> menu icon<br />
<br />
<pre class="brush:html"><header class="header">
<a href="/" class="logo">AGT</a>
<input class="menu-btn" type="checkbox" id="menu-btn" />
<label class="menu-icon" for="menu-btn"><span class="navicon"></span></label>
<ul class="menu">
<li><a href="#work">Our Work</a></li>
<li><a href="#about">About</a></li>
<li><a href="#careers">Careers</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</header>
</pre>
<div>
<br /></div>
<div>
We then need to add css to fix the header to the top of the page, reset list styling and hide the dropdown menu as the default state:</div>
<div>
<br /></div>
<div>
<pre class="brush:css">.header {
background-color: #fff;
box-shadow: 1px 1px 4px 0 rgba(0,0,0,.1);
position: fixed;
width: 100%;
z-index: 3;
}
.header ul {
margin: 0;
padding: 0;
list-style: none;
overflow: hidden;
background-color: #fff;
}
.header li a {
display: block;
padding: 20px 20px;
border-right: 1px solid #f4f4f4;
text-decoration: none;
}
.header li a:hover {
background-color: #f4f4f4;
}
.header .logo {
display: block;
float: left;
font-size: 2em;
padding: 10px 20px;
text-decoration: none;
}
.header .menu {
clear: both;
max-height: 0;
transition: max-height .2s ease-out;
}</pre>
</div>
<div>
<br />
Next we need to style the menu icon for the dropdown. I've decided to use span elements to create the icon, rather than an svg of icons font. Using css here allows us to have a fancy animation whenever some clicks the icon:</div>
<div>
<br /></div>
<div>
<pre class="brush:css">.header .menu-icon {
cursor: pointer;
display: inline-block;
float: right;
padding: 28px 20px;
position: relative;
user-select: none;
}
.header .menu-icon .navicon {
background: #333;
display: block;
height: 2px;
position: relative;
transition: background .2s ease-out;
width: 18px;
}
.header .menu-icon .navicon:before,
.header .menu-icon .navicon:after {
background: #333;
content: '';
display: block;
height: 100%;
position: absolute;
transition: all .2s ease-out;
width: 100%;
}
.header .menu-icon .navicon:before {
top: 5px;
}
.header .menu-icon .navicon:after {
top: -5px;
}</pre>
</div>
<div>
<br />
Now we can add the icon and menu animations when the checkbox is clicked:<br />
<br />
<pre class="brush:css">.header .menu-btn {
display: none;
}
.header .menu-btn:checked ~ .menu {
max-height: 240px;
}
.header .menu-btn:checked ~ .menu-icon .navicon {
background: transparent;
}
.header .menu-btn:checked ~ .menu-icon .navicon:before {
transform: rotate(-45deg);
}
.header .menu-btn:checked ~ .menu-icon .navicon:after {
transform: rotate(45deg);
}
.header .menu-btn:checked ~ .menu-icon:not(.steps) .navicon:before,
.header .menu-btn:checked ~ .menu-icon:not(.steps) .navicon:after {
top: 0;
}
</pre>
<br />
Lastly we need to add responsive css to show the horizontal menu items for larger browser widths by default:<br />
<br />
<pre class="brush:css">@media (min-width: 48em) {
.header li {
float: left;
}
.header li a {
padding: 20px 30px;
}
.header .menu {
clear: none;
float: right;
max-height: none;
}
.header .menu-icon {
display: none;
}
}</pre>
<br /></div>
<div>
And that's it, a responsive dropdown navigation with nice animations/icons, using css only.<br />
<br /></div>
<div>
You can view a working example here:<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="500" src="//jsfiddle.net/ug6f918s/423/embedded/result,html,css" width="100%"></iframe><br />
<br />
<a href="http://jsfiddle.net/ug6f918s/423/">http://jsfiddle.net/ug6f918s/423/</a></div>
<br />
Previous versions:<br />
<a href="http://jsfiddle.net/kmturley/88HRF/">http://jsfiddle.net/kmturley/88HRF/</a><br />
<br />
Previous example using a right-aligned mobile icon:<br />
<a href="http://jsfiddle.net/kmturley/04L0dkcj/10/">http://jsfiddle.net/kmturley/04L0dkcj/10/</a>
</div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0tag:blogger.com,1999:blog-9186252211939314092.post-35797911650133726372014-05-22T08:20:00.001-07:002017-06-13T12:33:05.550-07:00Native browser touch drag using overflow scroll<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgL2H8b3SqM3vPXqYmm4OFcXdDk7bPo8OgSh8j1yBxVS_2L68CFlrfTgzEx8GoJ5rFnTkEsM204MLBYe7EfnXhZecaRHATrt11YUf38E6STM4ys2D6RoPWxrMjmAE4cufJpEXcGLwVlTs0/s1600/touch.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="108" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgL2H8b3SqM3vPXqYmm4OFcXdDk7bPo8OgSh8j1yBxVS_2L68CFlrfTgzEx8GoJ5rFnTkEsM204MLBYe7EfnXhZecaRHATrt11YUf38E6STM4ys2D6RoPWxrMjmAE4cufJpEXcGLwVlTs0/s200/touch.jpg" width="200" /></a></div>
Mobiles and Tablets allow you to touch/drag scroll pages. This is a great interaction and the animation looks amazing. How can we get this functionality for desktop browsers using JavaScript? A look at some libraries and how to write your own.<br />
<br />
<a name='more'></a><br />
There are several libraries out there which can enable this for desktop browsers, one of the best being <a href="http://cubiq.org/iscroll-5" target="_blank">iScroll</a>. iScroll is great, but it has one main disadvantage. Content is moved to an absolutely positioned div to allow the scroll animation which means it's different from the mobile and tablet versions.<br />
<br />
Looking around there doesn't seem to be a solution which allows you to scroll the native browser scrollbars directly with an animation... well it must be possible... so lets make our own!<br />
<br />
First we want to identify the drag target, or fall back to use the document body instead, note that IE and firefox use documentElement instead of body to scroll a page:<br />
<br />
<pre class="brush: js">if (id) {
el = document.getElementById(id);
} else {
if (isIE || isFirefox) {
el = document.documentElement;
} else {
el = document.body;
}
}</pre>
<div>
<br /></div>
<div>
Next we need to add the event listeners for mouse down, move and up to the target element:<br />
<br />
<pre class="brush: js">function addEvent(name, el, func) {
if (el.addEventListener) {
el.addEventListener(name, func, false);
} else if (el.attachEvent) {
el.attachEvent('on' + name, func);
} else {
el[name] = func;
}
}
addEvent('mousedown', el, onMouseDown);
addEvent('mousemove', el, onMouseMove);
addEvent('mouseup', el, onMouseUp);</pre>
</div>
<div>
<br /></div>
<div>
The mouse down function has a check for IE to ensure the event is captured and prevents dragging if the image is the target:<br />
<br />
<pre class="brush: js">function onMouseDown(e) {
if (!e) { e = window.event; }
if (e.target && e.target.nodeName === 'IMG') {
e.preventDefault();
} else if (e.srcElement && e.srcElement.nodeName === 'IMG') {
e.returnValue = false;
}
startx = e.clientX + el.scrollLeft;
starty = e.clientY + el.scrollTop;
diffx = 0;
diffy = 0;
drag = true;
}</pre>
</div>
<div>
<br /></div>
<div>
<div>
The mouse move function updates the values only if the user is holding down the mouse button:<br />
<br />
<pre class="brush: js">function onMouseMove(e) {
if (drag === true) {
if (!e) { e = window.event; }
diffx = (this.startx - (e.clientX + el.scrollLeft));
diffy = (this.starty - (e.clientY + el.scrollTop));
el.scrollLeft += diffx;
el.scrollTop += diffy;
}
}</pre>
</div>
</div>
<div>
<br /></div>
<div>
<div>
And the mouse up function has the magic, it takes the move values and gradually slows down the values using request animation frame to make a smooth animation:<br />
<br />
<pre class="brush: js">onMouseUp: function (e) {
if (!e) { e = window.event; }
drag = false;
var start = 1,
animate = function () {
var step = Math.sin(start);
if (step <= 0) {
window.cancelAnimationFrame(animate);
} else {
el.scrollLeft += diffx * step;
el.scrollTop += diffy * step;
start -= 0.02;
window.requestAnimationFrame(animate);
}
};
animate();
}</pre>
</div>
</div>
<div>
<br /></div>
<div>
To support requestAnimationFrame in older browsers you will need to include a shim script such as this one:</div>
<div>
<a href="https://gist.github.com/paulirish/1579671">https://gist.github.com/paulirish/1579671</a><br />
<br />
<div>
Here is the source:</div>
<div>
<a href="https://github.com/kmturley/touch-scroll">https://github.com/kmturley/touch-scroll</a></div>
</div>
<div>
<br /></div>
<div>
And an example of the script working (IE8+, Firefox, Safari, Chrome):<br />
<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="600" src="https://kmturley.github.io/touch-scroll/" width="100%"></iframe>
<a href="https://kmturley.github.io/touch-scroll/">https://kmturley.github.io/touch-scroll/</a></div>
<div>
<br /></div>
<div>
<br /></div>
</div>
Anonymoushttp://www.blogger.com/profile/08077693629722557054noreply@blogger.com0