Initial commit

This commit is contained in:
Juho Teperi
2013-05-13 11:43:34 +03:00
commit fbf8fb087a
44 changed files with 2425 additions and 0 deletions

3
.bowerrc Normal file
View File

@@ -0,0 +1,3 @@
{
"directory": "app/components"
}

29
.editorconfig Normal file
View File

@@ -0,0 +1,29 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# Change these settings to your own preference
indent_style = space
indent_size = 2
# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.py]
indent_size = 4
[*.md]
trim_trailing_whitespace = false
[*.sublime-project]
indent_style = tab

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
node_modules
dist
.tmp
app/components
*.sublime-workspace

26
.jshintrc Normal file
View File

@@ -0,0 +1,26 @@
{
"node": true,
"browser": true,
"es5": true,
"esnext": true,
"bitwise": true,
"camelcase": true,
"curly": true,
"eqeqeq": true,
"immed": true,
"indent": 2,
"latedef": true,
"newcap": true,
"noarg": true,
"quotmark": "single",
"regexp": true,
"undef": true,
"unused": false,
"strict": true,
"trailing": true,
"smarttabs": true,
"globals": {
"$": true,
"define": true
}
}

320
Gruntfile.js Normal file
View File

@@ -0,0 +1,320 @@
'use strict';
var lrSnippet = require('grunt-contrib-livereload/lib/utils').livereloadSnippet;
var mountFolder = function (connect, dir) {
return connect.static(require('path').resolve(dir));
};
module.exports = function (grunt) {
// load all grunt tasks
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
// configurable paths
var yeomanConfig = {
app: 'app',
dist: 'dist'
};
grunt.initConfig({
yeoman: yeomanConfig,
watch: {
less: {
files: ['<%= yeoman.app %>/styles/*.less'],
tasks: ['less']
},
livereload: {
files: [
'<%= yeoman.app %>/*.html',
'{.tmp,<%= yeoman.app %>}/styles/*.css',
'{.tmp,<%= yeoman.app %>}/scripts/**/*.js',
'<%= yeoman.app %>/images/*.{png,jpg,jpeg,webp}'
],
tasks: ['livereload']
},
handlebars: {
files: [
'app/templates/**/*.hbs'
],
tasks: ['handlebars', 'livereload']
}
},
handlebars: {
compile: {
files: {
'.tmp/scripts/templates.js': [
'app/templates/**/*.hbs'
]
},
options: {
// namespace: false,
amd: true,
processName: function (filename) {
return filename.replace(/^app\/templates\//, '').replace(/\.hbs/, '');
}
}
}
},
connect: {
options: {
port: 9000,
// change this to '0.0.0.0' to access the server from outside
hostname: 'localhost'
},
livereload: {
options: {
middleware: function (connect) {
return [
lrSnippet,
mountFolder(connect, '.tmp'),
mountFolder(connect, 'app')
];
}
}
},
dist: {
options: {
middleware: function (connect) {
return [
mountFolder(connect, 'dist')
];
}
}
}
},
open: {
server: {
url: 'http://localhost:<%= connect.options.port %>'
}
},
clean: {
dist: ['.tmp', '<%= yeoman.dist %>/*'],
server: '.tmp'
},
jshint: {
options: {
jshintrc: '.jshintrc'
},
all: [
'Gruntfile.js',
'<%= yeoman.app %>/scripts/*.js'
]
},
less: {
dist: {
options: {
paths: ['app/components'],
yuicompress: true,
strictUnits: false,
strictMaths: false
},
files: {
'.tmp/styles/main.css': '<%= yeoman.app %>/styles/main.less'
}
},
server: {
options: {
paths: ['app/components'],
strictUnits: false,
strictMaths: false
},
files: {
'.tmp/styles/main.css': '<%= yeoman.app %>/styles/main.less'
}
}
},
requirejs: {
dist: {
// Options: https://github.com/jrburke/r.js/blob/master/build/example.build.js
options: {
// `name` and `out` is set by grunt-usemin
baseUrl: 'app/scripts',
paths: {
'bootstrap': '../../.tmp/scripts/bootstrap',
'Template': '../../.tmp/scripts/templates'
},
optimize: 'none',
// TODO: Figure out how to make sourcemaps work with grunt-usemin
// https://github.com/yeoman/grunt-usemin/issues/30
//generateSourceMaps: true,
// required to support SourceMaps
// http://requirejs.org/docs/errors.html#sourcemapcomments
preserveLicenseComments: false,
useStrict: true,
wrap: true,
include: [
'libs/gmaps'
]
}
}
},
useminPrepare: {
html: '<%= yeoman.app %>/index.html',
options: {
dest: '<%= yeoman.dist %>'
}
},
usemin: {
html: ['<%= yeoman.dist %>/*.html'],
css: ['<%= yeoman.dist %>/styles/*.css'],
options: {
dirs: ['<%= yeoman.dist %>']
}
},
imagemin: {
dist: {
files: [{
expand: true,
cwd: '<%= yeoman.app %>/images',
src: '*.{png,jpg,jpeg}',
dest: '<%= yeoman.dist %>/images'
}]
}
},
cssmin: {
dist: {
files: {
'<%= yeoman.dist %>/styles/main.css': [
'.tmp/styles/*.css',
'<%= yeoman.app %>/styles/*.css'
]
}
}
},
htmlmin: {
dist: {
options: {
/*removeCommentsFromCDATA: true,
// https://github.com/yeoman/grunt-usemin/issues/44
//collapseWhitespace: true,
collapseBooleanAttributes: true,
removeAttributeQuotes: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeOptionalTags: true*/
},
files: [{
expand: true,
cwd: '<%= yeoman.app %>',
src: '*.html',
dest: '<%= yeoman.dist %>'
}]
}
},
copy: {
dist: {
files: [{
expand: true,
dot: true,
cwd: '<%= yeoman.app %>',
dest: '<%= yeoman.dist %>',
src: [
'api/*',
'*.{ico,txt}',
'.htaccess',
]
}, {
expand: true,
flatten: true,
cwd: '<%= yeoman.app %>',
dest: '<%= yeoman.dist %>/font',
src: [
'components/font-awesome/build/assets/font-awesome/font/*.{ttf,woff,otf,eot,svg}'
]
}, {
expand: true,
flatten: true,
cwd: '<%= yeoman.app %>',
dest: '<%= yeoman.dist %>/styles',
src: [
'components/select2/*.{png,gif}'
]
}]
},
livereload: {
files: [{
expand: true,
flatten: true,
cwd: '<%= yeoman.app %>',
dest: '.tmp/font',
src: [
'components/font-awesome/build/assets/font-awesome/font/*.{ttf,woff,otf,eot,svg}'
]
}, {
expand: true,
flatten: true,
cwd: '<%= yeoman.app %>',
dest: '.tmp/styles',
src: [
'components/select2/*.{png,gif}'
]
}]
}
},
bower: {
all: {
rjsConfig: '<%= yeoman.app %>/scripts/main.js'
}
},
concat: {
bootstrap: {
src: [
'app/components/bootstrap/js/bootstrap-transition.js',
'app/components/bootstrap/js/bootstrap-alert.js',
'app/components/bootstrap/js/bootstrap-button.js',
'app/components/bootstrap/js/bootstrap-carousel.js',
'app/components/bootstrap/js/bootstrap-collapse.js',
'app/components/bootstrap/js/bootstrap-dropdown.js',
'app/components/bootstrap/js/bootstrap-modal.js',
'app/components/bootstrap/js/bootstrap-tooltip.js',
'app/components/bootstrap/js/bootstrap-popover.js',
'app/components/bootstrap/js/bootstrap-scrollspy.js',
'app/components/bootstrap/js/bootstrap-tab.js',
'app/components/bootstrap/js/bootstrap-typeahead.js',
'app/components/bootstrap/js/bootstrap-affix.js'
],
dest: '.tmp/scripts/bootstrap.js'
}
}
});
grunt.renameTask('regarde', 'watch');
grunt.registerTask('server', function (target) {
if (target === 'dist') {
return grunt.task.run(['build', 'open', 'connect:dist:keepalive']);
}
grunt.task.run([
'clean:server',
'less:server',
'handlebars',
'concat',
'copy:livereload',
'livereload-start',
'connect:livereload',
'open',
'watch'
]);
});
grunt.registerTask('build', [
'clean:dist',
'less:dist',
'handlebars',
'concat',
'useminPrepare',
'requirejs',
'imagemin',
'htmlmin',
'concat',
'cssmin',
'uglify',
'copy',
'usemin'
]);
grunt.registerTask('default', [
'jshint',
'build'
]);
};

543
app/.htaccess Normal file
View File

@@ -0,0 +1,543 @@
# Apache configuration file
# httpd.apache.org/docs/2.2/mod/quickreference.html
# Note .htaccess files are an overhead, this logic should be in your Apache
# config if possible: httpd.apache.org/docs/2.2/howto/htaccess.html
# Techniques in here adapted from all over, including:
# Kroc Camen: camendesign.com/.htaccess
# perishablepress.com/press/2006/01/10/stupid-htaccess-tricks/
# Sample .htaccess file of CMS MODx: modxcms.com
# ----------------------------------------------------------------------
# Better website experience for IE users
# ----------------------------------------------------------------------
# Force the latest IE version, in various cases when it may fall back to IE7 mode
# github.com/rails/rails/commit/123eb25#commitcomment-118920
# Use ChromeFrame if it's installed for a better experience for the poor IE folk
<IfModule mod_headers.c>
Header set X-UA-Compatible "IE=Edge,chrome=1"
# mod_headers can't match by content-type, but we don't want to send this header on *everything*...
<FilesMatch "\.(appcache|crx|css|eot|gif|htc|ico|jpe?g|js|m4a|m4v|manifest|mp4|oex|oga|ogg|ogv|otf|pdf|png|safariextz|svg|svgz|ttf|vcf|webm|webp|woff|xml|xpi)$">
Header unset X-UA-Compatible
</FilesMatch>
</IfModule>
# ----------------------------------------------------------------------
# Cross-domain AJAX requests
# ----------------------------------------------------------------------
# Serve cross-domain Ajax requests, disabled by default.
# enable-cors.org
# code.google.com/p/html5security/wiki/CrossOriginRequestSecurity
# <IfModule mod_headers.c>
# Header set Access-Control-Allow-Origin "*"
# </IfModule>
# ----------------------------------------------------------------------
# CORS-enabled images (@crossorigin)
# ----------------------------------------------------------------------
# Send CORS headers if browsers request them; enabled by default for images.
# developer.mozilla.org/en/CORS_Enabled_Image
# blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html
# hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/
# wiki.mozilla.org/Security/Reviews/crossoriginAttribute
<IfModule mod_setenvif.c>
<IfModule mod_headers.c>
# mod_headers, y u no match by Content-Type?!
<FilesMatch "\.(gif|ico|jpe?g|png|svg|svgz|webp)$">
SetEnvIf Origin ":" IS_CORS
Header set Access-Control-Allow-Origin "*" env=IS_CORS
</FilesMatch>
</IfModule>
</IfModule>
# ----------------------------------------------------------------------
# Webfont access
# ----------------------------------------------------------------------
# Allow access from all domains for webfonts.
# Alternatively you could only whitelist your
# subdomains like "subdomain.example.com".
<IfModule mod_headers.c>
<FilesMatch "\.(eot|font.css|otf|ttc|ttf|woff)$">
Header set Access-Control-Allow-Origin "*"
</FilesMatch>
</IfModule>
# ----------------------------------------------------------------------
# Proper MIME type for all files
# ----------------------------------------------------------------------
# JavaScript
# Normalize to standard type (it's sniffed in IE anyways)
# tools.ietf.org/html/rfc4329#section-7.2
AddType application/javascript js jsonp
AddType application/json json
# Audio
AddType audio/mp4 m4a f4a f4b
AddType audio/ogg oga ogg
# Video
AddType video/mp4 mp4 m4v f4v f4p
AddType video/ogg ogv
AddType video/webm webm
AddType video/x-flv flv
# SVG
# Required for svg webfonts on iPad
# twitter.com/FontSquirrel/status/14855840545
AddType image/svg+xml svg svgz
AddEncoding gzip svgz
# Webfonts
AddType application/vnd.ms-fontobject eot
AddType application/x-font-ttf ttf ttc
AddType application/x-font-woff woff
AddType font/opentype otf
# Assorted types
AddType application/octet-stream safariextz
AddType application/x-chrome-extension crx
AddType application/x-opera-extension oex
AddType application/x-shockwave-flash swf
AddType application/x-web-app-manifest+json webapp
AddType application/x-xpinstall xpi
AddType application/xml rss atom xml rdf
AddType image/webp webp
AddType image/x-icon ico
AddType text/cache-manifest appcache manifest
AddType text/vtt vtt
AddType text/x-component htc
AddType text/x-vcard vcf
# ----------------------------------------------------------------------
# Allow concatenation from within specific js and css files
# ----------------------------------------------------------------------
# e.g. Inside of script.combined.js you could have
# <!--#include file="libs/jquery-1.5.0.min.js" -->
# <!--#include file="plugins/jquery.idletimer.js" -->
# and they would be included into this single file.
# This is not in use in the boilerplate as it stands. You may
# choose to use this technique if you do not have a build process.
#<FilesMatch "\.combined\.js$">
# Options +Includes
# AddOutputFilterByType INCLUDES application/javascript application/json
# SetOutputFilter INCLUDES
#</FilesMatch>
#<FilesMatch "\.combined\.css$">
# Options +Includes
# AddOutputFilterByType INCLUDES text/css
# SetOutputFilter INCLUDES
#</FilesMatch>
# ----------------------------------------------------------------------
# Gzip compression
# ----------------------------------------------------------------------
<IfModule mod_deflate.c>
# Force deflate for mangled headers developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping/
<IfModule mod_setenvif.c>
<IfModule mod_headers.c>
SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
</IfModule>
</IfModule>
# Compress all output labeled with one of the following MIME-types
# (for Apache versions below 2.3.7, you don't need to enable `mod_filter`
# and can remove the `<IfModule mod_filter.c>` and `</IfModule>` lines as
# `AddOutputFilterByType` is still in the core directives)
<IfModule mod_filter.c>
AddOutputFilterByType DEFLATE application/atom+xml \
application/javascript \
application/json \
application/rss+xml \
application/vnd.ms-fontobject \
application/x-font-ttf \
application/xhtml+xml \
application/xml \
font/opentype \
image/svg+xml \
image/x-icon \
text/css \
text/html \
text/plain \
text/x-component \
text/xml
</IfModule>
</IfModule>
# ----------------------------------------------------------------------
# Expires headers (for better cache control)
# ----------------------------------------------------------------------
# These are pretty far-future expires headers.
# They assume you control versioning with filename-based cache busting
# Additionally, consider that outdated proxies may miscache
# www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/
# If you don't use filenames to version, lower the CSS and JS to something like
# "access plus 1 week".
<IfModule mod_expires.c>
ExpiresActive on
# Perhaps better to whitelist expires rules? Perhaps.
ExpiresDefault "access plus 1 month"
# cache.appcache needs re-requests in FF 3.6 (thanks Remy ~Introducing HTML5)
ExpiresByType text/cache-manifest "access plus 0 seconds"
# Your document html
ExpiresByType text/html "access plus 0 seconds"
# Data
ExpiresByType application/json "access plus 0 seconds"
ExpiresByType application/xml "access plus 0 seconds"
ExpiresByType text/xml "access plus 0 seconds"
# Feed
ExpiresByType application/atom+xml "access plus 1 hour"
ExpiresByType application/rss+xml "access plus 1 hour"
# Favicon (cannot be renamed)
ExpiresByType image/x-icon "access plus 1 week"
# Media: images, video, audio
ExpiresByType audio/ogg "access plus 1 month"
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType video/mp4 "access plus 1 month"
ExpiresByType video/ogg "access plus 1 month"
ExpiresByType video/webm "access plus 1 month"
# HTC files (css3pie)
ExpiresByType text/x-component "access plus 1 month"
# Webfonts
ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
ExpiresByType application/x-font-ttf "access plus 1 month"
ExpiresByType application/x-font-woff "access plus 1 month"
ExpiresByType font/opentype "access plus 1 month"
ExpiresByType image/svg+xml "access plus 1 month"
# CSS and JavaScript
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType text/css "access plus 1 year"
</IfModule>
# ----------------------------------------------------------------------
# Prevent mobile network providers from modifying your site
# ----------------------------------------------------------------------
# The following header prevents modification of your code over 3G on some
# European providers.
# This is the official 'bypass' suggested by O2 in the UK.
# <IfModule mod_headers.c>
# Header set Cache-Control "no-transform"
# </IfModule>
# ----------------------------------------------------------------------
# ETag removal
# ----------------------------------------------------------------------
# FileETag None is not enough for every server.
<IfModule mod_headers.c>
Header unset ETag
</IfModule>
# Since we're sending far-future expires, we don't need ETags for
# static content.
# developer.yahoo.com/performance/rules.html#etags
FileETag None
# ----------------------------------------------------------------------
# Stop screen flicker in IE on CSS rollovers
# ----------------------------------------------------------------------
# The following directives stop screen flicker in IE on CSS rollovers - in
# combination with the "ExpiresByType" rules for images (see above).
# BrowserMatch "MSIE" brokenvary=1
# BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1
# BrowserMatch "Opera" !brokenvary
# SetEnvIf brokenvary 1 force-no-vary
# ----------------------------------------------------------------------
# Set Keep-Alive Header
# ----------------------------------------------------------------------
# Keep-Alive allows the server to send multiple requests through one
# TCP-connection. Be aware of possible disadvantages of this setting. Turn on
# if you serve a lot of static content.
# <IfModule mod_headers.c>
# Header set Connection Keep-Alive
# </IfModule>
# ----------------------------------------------------------------------
# Cookie setting from iframes
# ----------------------------------------------------------------------
# Allow cookies to be set from iframes (for IE only)
# If needed, specify a path or regex in the Location directive.
# <IfModule mod_headers.c>
# Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
# </IfModule>
# ----------------------------------------------------------------------
# Start rewrite engine
# ----------------------------------------------------------------------
# Turning on the rewrite engine is necessary for the following rules and
# features. FollowSymLinks must be enabled for this to work.
# Some cloud hosting services require RewriteBase to be set: goo.gl/HOcPN
# If using the h5bp in a subdirectory, use `RewriteBase /foo` instead where
# 'foo' is your directory.
# If your web host doesn't allow the FollowSymlinks option, you may need to
# comment it out and use `Options +SymLinksIfOwnerMatch`, but be aware of the
# performance impact: http://goo.gl/Mluzd
<IfModule mod_rewrite.c>
Options +FollowSymlinks
# Options +SymLinksIfOwnerMatch
RewriteEngine On
# RewriteBase /
</IfModule>
# ----------------------------------------------------------------------
# Suppress or force the "www." at the beginning of URLs
# ----------------------------------------------------------------------
# The same content should never be available under two different URLs -
# especially not with and without "www." at the beginning, since this can cause
# SEO problems (duplicate content). That's why you should choose one of the
# alternatives and redirect the other one.
# By default option 1 (no "www.") is activated.
# no-www.org/faq.php?q=class_b
# If you'd prefer to use option 2, just comment out all option 1 lines
# and uncomment option 2.
# IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME!
# ----------------------------------------------------------------------
# Option 1:
# Rewrite "www.example.com -> example.com".
<IfModule mod_rewrite.c>
RewriteCond %{HTTPS} !=on
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
</IfModule>
# ----------------------------------------------------------------------
# Option 2:
# Rewrite "example.com -> www.example.com".
# Be aware that the following rule might not be a good idea if you use "real"
# subdomains for certain parts of your website.
# <IfModule mod_rewrite.c>
# RewriteCond %{HTTPS} !=on
# RewriteCond %{HTTP_HOST} !^www\..+$ [NC]
# RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# </IfModule>
# ----------------------------------------------------------------------
# Built-in filename-based cache busting
# ----------------------------------------------------------------------
# If you're not using the build script to manage your filename version revving,
# you might want to consider enabling this, which will route requests for
# `/css/style.20110203.css` to `/css/style.css`.
# To understand why this is important and a better idea than all.css?v1231,
# please refer to the bundled documentation about `.htaccess`.
# <IfModule mod_rewrite.c>
# RewriteCond %{REQUEST_FILENAME} !-f
# RewriteCond %{REQUEST_FILENAME} !-d
# RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L]
# </IfModule>
# ----------------------------------------------------------------------
# Prevent SSL cert warnings
# ----------------------------------------------------------------------
# Rewrite secure requests properly to prevent SSL cert warnings, e.g. prevent
# https://www.example.com when your cert only allows https://secure.example.com
# <IfModule mod_rewrite.c>
# RewriteCond %{SERVER_PORT} !^443
# RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L]
# </IfModule>
# ----------------------------------------------------------------------
# Prevent 404 errors for non-existing redirected folders
# ----------------------------------------------------------------------
# without -MultiViews, Apache will give a 404 for a rewrite if a folder of the
# same name does not exist.
# webmasterworld.com/apache/3808792.htm
Options -MultiViews
# ----------------------------------------------------------------------
# Custom 404 page
# ----------------------------------------------------------------------
# You can add custom pages to handle 500 or 403 pretty easily, if you like.
# If you are hosting your site in subdirectory, adjust this accordingly
# e.g. ErrorDocument 404 /subdir/404.html
ErrorDocument 404 /404.html
# ----------------------------------------------------------------------
# UTF-8 encoding
# ----------------------------------------------------------------------
# Use UTF-8 encoding for anything served text/plain or text/html
AddDefaultCharset utf-8
# Force UTF-8 for a number of file formats
AddCharset utf-8 .atom .css .js .json .rss .vtt .xml
# ----------------------------------------------------------------------
# A little more security
# ----------------------------------------------------------------------
# To avoid displaying the exact version number of Apache being used, add the
# following to httpd.conf (it will not work in .htaccess):
# ServerTokens Prod
# "-Indexes" will have Apache block users from browsing folders without a
# default document Usually you should leave this activated, because you
# shouldn't allow everybody to surf through every folder on your server (which
# includes rather private places like CMS system folders).
<IfModule mod_autoindex.c>
Options -Indexes
</IfModule>
# Block access to "hidden" directories or files whose names begin with a
# period. This includes directories used by version control systems such as
# Subversion or Git.
<IfModule mod_rewrite.c>
RewriteCond %{SCRIPT_FILENAME} -d [OR]
RewriteCond %{SCRIPT_FILENAME} -f
RewriteRule "(^|/)\." - [F]
</IfModule>
# Block access to backup and source files. These files may be left by some
# text/html editors and pose a great security danger, when anyone can access
# them.
<FilesMatch "(\.(bak|config|dist|fla|inc|ini|log|psd|sh|sql|swp)|~)$">
Order allow,deny
Deny from all
Satisfy All
</FilesMatch>
# If your server is not already configured as such, the following directive
# should be uncommented in order to set PHP's register_globals option to OFF.
# This closes a major security hole that is abused by most XSS (cross-site
# scripting) attacks. For more information: http://php.net/register_globals
#
# IF REGISTER_GLOBALS DIRECTIVE CAUSES 500 INTERNAL SERVER ERRORS:
#
# Your server does not allow PHP directives to be set via .htaccess. In that
# case you must make this change in your php.ini file instead. If you are
# using a commercial web host, contact the administrators for assistance in
# doing this. Not all servers allow local php.ini files, and they should
# include all PHP configurations (not just this one), or you will effectively
# reset everything to PHP defaults. Consult www.php.net for more detailed
# information about setting PHP directives.
# php_flag register_globals Off
# Rename session cookie to something else, than PHPSESSID
# php_value session.name sid
# Disable magic quotes (This feature has been DEPRECATED as of PHP 5.3.0 and REMOVED as of PHP 5.4.0.)
# php_flag magic_quotes_gpc Off
# Do not show you are using PHP
# Note: Move this line to php.ini since it won't work in .htaccess
# php_flag expose_php Off
# Level of log detail - log all errors
# php_value error_reporting -1
# Write errors to log file
# php_flag log_errors On
# Do not display errors in browser (production - Off, development - On)
# php_flag display_errors Off
# Do not display startup errors (production - Off, development - On)
# php_flag display_startup_errors Off
# Format errors in plain text
# Note: Leave this setting 'On' for xdebug's var_dump() output
# php_flag html_errors Off
# Show multiple occurrence of error
# php_flag ignore_repeated_errors Off
# Show same errors from different sources
# php_flag ignore_repeated_source Off
# Size limit for error messages
# php_value log_errors_max_len 1024
# Don't precede error with string (doesn't accept empty string, use whitespace if you need)
# php_value error_prepend_string " "
# Don't prepend to error (doesn't accept empty string, use whitespace if you need)
# php_value error_append_string " "
# Increase cookie security
<IfModule mod_php5.c>
php_value session.cookie_httponly true
</IfModule>

157
app/404.html Normal file
View File

@@ -0,0 +1,157 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Page Not Found :(</title>
<style>
::-moz-selection {
background: #b3d4fc;
text-shadow: none;
}
::selection {
background: #b3d4fc;
text-shadow: none;
}
html {
padding: 30px 10px;
font-size: 20px;
line-height: 1.4;
color: #737373;
background: #f0f0f0;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
html,
input {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
body {
max-width: 500px;
_width: 500px;
padding: 30px 20px 50px;
border: 1px solid #b3b3b3;
border-radius: 4px;
margin: 0 auto;
box-shadow: 0 1px 10px #a7a7a7, inset 0 1px 0 #fff;
background: #fcfcfc;
}
h1 {
margin: 0 10px;
font-size: 50px;
text-align: center;
}
h1 span {
color: #bbb;
}
h3 {
margin: 1.5em 0 0.5em;
}
p {
margin: 1em 0;
}
ul {
padding: 0 0 0 40px;
margin: 1em 0;
}
.container {
max-width: 380px;
_width: 380px;
margin: 0 auto;
}
/* google search */
#goog-fixurl ul {
list-style: none;
padding: 0;
margin: 0;
}
#goog-fixurl form {
margin: 0;
}
#goog-wm-qt,
#goog-wm-sb {
border: 1px solid #bbb;
font-size: 16px;
line-height: normal;
vertical-align: top;
color: #444;
border-radius: 2px;
}
#goog-wm-qt {
width: 220px;
height: 20px;
padding: 5px;
margin: 5px 10px 0 0;
box-shadow: inset 0 1px 1px #ccc;
}
#goog-wm-sb {
display: inline-block;
height: 32px;
padding: 0 10px;
margin: 5px 0 0;
white-space: nowrap;
cursor: pointer;
background-color: #f5f5f5;
background-image: -webkit-linear-gradient(rgba(255,255,255,0), #f1f1f1);
background-image: -moz-linear-gradient(rgba(255,255,255,0), #f1f1f1);
background-image: -ms-linear-gradient(rgba(255,255,255,0), #f1f1f1);
background-image: -o-linear-gradient(rgba(255,255,255,0), #f1f1f1);
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
*overflow: visible;
*display: inline;
*zoom: 1;
}
#goog-wm-sb:hover,
#goog-wm-sb:focus {
border-color: #aaa;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
background-color: #f8f8f8;
}
#goog-wm-qt:hover,
#goog-wm-qt:focus {
border-color: #105cb6;
outline: 0;
color: #222;
}
input::-moz-focus-inner {
padding: 0;
border: 0;
}
</style>
</head>
<body>
<div class="container">
<h1>Not found <span>:(</span></h1>
<p>Sorry, but the page you were trying to view does not exist.</p>
<p>It looks like this was the result of either:</p>
<ul>
<li>a mistyped address</li>
<li>an out-of-date link</li>
</ul>
<script>
var GOOG_FIXURL_LANG = (navigator.language || '').slice(0,2),GOOG_FIXURL_SITE = location.host;
</script>
<script src="http://linkhelp.clients.google.com/tbproxy/lh/wm/fixurl.js"></script>
</div>
</body>
</html>

20
app/api/config.php Normal file
View File

@@ -0,0 +1,20 @@
<?php
$config = array(
'server' => 'localhost',
'db' => 'lentolaskuri2',
'user' => 'lentolaskuri2',
'password' => '8@89z~UIwavn',
);
// ----
$mysqli = new mysqli($config['server'], $config['user'], $config['password'], $config['db']);
/* check connection */
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}

28
app/api/import.php Normal file
View File

@@ -0,0 +1,28 @@
<?php
require_once('config.php');
if (($handle = fopen("http://openflights.svn.sourceforge.net/viewvc/openflights/openflights/data/airports.dat", "r")) !== FALSE) {
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
$id = $mysqli->real_escape_string($data[0]);
$name = $mysqli->real_escape_string($data[1]);
$city = $mysqli->real_escape_string($data[2]);
$country = $mysqli->real_escape_string($data[3]);
$iata = $mysqli->real_escape_string($data[4]);
$icao = $mysqli->real_escape_string($data[5]);
if (empty($iata)) {
continue;
}
$lat = $mysqli->real_escape_string($data[6]);
$long = $mysqli->real_escape_string($data[7]);
$query = "REPLACE INTO airports (id, name, city, country, iata, icao, lat, `long`) VALUES ('$id', '$name', '$city', '$country', '$iata', '$icao', '$lat', '$long')";
if (!$mysqli->query($query)) {
printf("Error: %s\n", $mysqli->sqlstate);
exit();
}
}
fclose($handle);
}
$mysqli->close();

54
app/api/search.php Normal file
View File

@@ -0,0 +1,54 @@
<?php
require_once('config.php');
header("Access-Control-Allow-Origin: *");
$search = $mysqli->real_escape_string($_GET['s']);
$id = $mysqli->real_escape_string($_GET['i']);
if (empty($search) && empty($id)) {
exit();
}
$query = "";
if (!empty($search)) {
$query = "
SELECT *,
CASE
WHEN iata LIKE '$search%' THEN 100
WHEN name LIKE '$search%' THEN 75
WHEN name LIKE '%$search%' THEN 74
WHEN city LIKE '$search%' THEN 70
WHEN country LIKE '$search%' THEN 65
WHEN city LIKE '%$search%' THEN 60
WHEN country LIKE '%$search%' THEN 55
ELSE 0 END
AS score
FROM airports
WHERE
name LIKE '$search%' OR
name LIKE '%$search%' OR
country LIKE '$search%' OR
country LIKE '%$search%' OR
city LIKE '$search%' OR
city LIKE '%$search%' OR
iata LIKE '$search%'
ORDER BY score DESC
LIMIT 10;
";
} else if (!empty($id)) {
$query ="SELECT * FROM airports WHERE id LIKE '$id';";
}
$results = array();
if ($result = $mysqli->query($query)) {
while ($row = $result->fetch_assoc()) {
$results[] = $row;
}
print(json_encode($results));
} else {
printf("Error: %s\n", $mysqli->sqlstate);
}

BIN
app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

77
app/index.html Normal file
View File

@@ -0,0 +1,77 @@
<!doctype html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Lentolaskuri 2</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="styles/main.css">
</head>
<body data-views="calculator maps">
<div class="container calculator">
<div class="row">
<div class="span12">
<h1>Lentolaskuri2</h1>
</div>
</div>
<div class="row">
<div class="span3 passengersWidget">
<legend>
Matkustajia
</legend>
<input type="number" placeholder="1" class="span3" />
</div>
<div class="span3 roundtripWidget">
<!-- <legend>Meno-paluu</legend> -->
<legend>&nbsp;</legend>
<div class="btn-group">
<button type="button" class="btn active">Meno</button>
<button type="button" class="btn roundtrip">Meno-paluu</button>
</div>
</div>
</div>
<div class="row">
<div class="span6">
<legend>Reitti</legend>
<div class="route"></div>
</div>
</div>
<div class="controls row-fluid">
<div class="span6 legInputWidget">
<button class="btn btn-block"><span>Valitse lentokenttä</span> <i class="caret"></i></button>
<input type="hidden"/>
</div>
</div>
<div class="row-fluid">
<div class="span6 totalWidget"></div>
</div>
<div class="row-fluid">
<div class="span6">
<ul class="nav nav-tabs">
<li class="active"><a href="#map" data-toggle="tab">Kartta</a></li>
<li><a href="#operation" data-toggle="tab">Laskurin toiminta</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="map">
<div id="gmap" class="map"></div>
</div>
<div class="tab-pane" id="operation">
</div>
</div>
</div>
<!-- build:js scripts/main.js -->
<script data-main="scripts/main" src="components/requirejs/require.js"></script>
<!-- endbuild -->
</body>
</html>

3
app/robots.txt Normal file
View File

@@ -0,0 +1,3 @@
# robotstxt.org
User-agent: *

40
app/scripts/app.js Normal file
View File

@@ -0,0 +1,40 @@
'use strict';
define([
'collections/route',
'collections/results',
'views/route',
'views/leginput',
'views/roundtrip',
'views/passengers',
'views/map',
'views/total',
'views/operation'
], function (Route, Results, RouteView, LegInput, RoundtripInput, PassengersInput, MapView, TotalView, OperationView) {
var App = function () {
this.initialize = function () {
this.route = new Route();
this.legInput = new LegInput({collection: this.route});
this.roundtripInput = new RoundtripInput();
this.passengersInput = new PassengersInput();
this.routeView = new RouteView({collection: this.route});
this.mapView = new MapView({collection: this.route});
// Order is important. ResultsView requires that Route legs are
// on dom as ResultView references from and to legs to calculate
// position.
this.results = new Results([], {route: this.route});
this.totalView = new TotalView({model: this.results.total});
new OperationView();
};
this.initialize();
};
return App;
});

View File

@@ -0,0 +1,53 @@
'use strict';
define([
'backbone',
'models/result',
'models/total'
], function (Backbone, Result, Total) {
var Results = Backbone.Collection.extend({
model: Result,
initialize: function (model, opts) {
this.route = opts.route;
this.route.on('add', this.newLeg, this);
this.route.on('remove', this.calculate, this);
this.route.on('sort', this.calculate, this);
this.total = new Total();
},
newLeg: function (model, collection, options) {
if (this.route.length < 2) {
return;
}
var from;
if (this.length > 0) {
from = this.route.get(this.last().get('to'));
} else {
from = this.route.first();
}
var result = new Result({from: from, to: model});
this.push(result);
this.total.add(result);
},
calculate: function (model, collection, options) {
var models = [];
this.total.reset();
var prev = null;
this.route.forEach(function (leg) {
if (prev) {
var result = new Result({from: prev, to: leg});
models.push(result);
this.total.add(result);
}
prev = leg;
}.bind(this));
this.reset(models);
}
});
return Results;
});

View File

@@ -0,0 +1,19 @@
define([
'lodash',
'backbone',
'../models/leg'
], function (_, Backbone, Leg) {
// var stages = [];
// var data = {};
var num = 1;
var Route = Backbone.Collection.extend({
model: Leg,
comparator: function (model) {
return model.get('order');
}
});
return Route;
});

64
app/scripts/config.js Normal file
View File

@@ -0,0 +1,64 @@
'use strict';
define(function () {
return {
api: 'http://work.lentolaskuri.fi/api',
R: 6371,
radiativeForceFactor: function (dist) {
return (dist >= 500) ? 2.0 : 1.0;
},
distanceRanges: [{
name: '0',
}, {
name: '500',
min: 500
}, {
name: '1400',
min: 1400
}, {
name: '5500',
min: 5500,
}, {
name: '9000',
min: 9000
}],
parameters: [{
name: 'Suomi',
regex: /^EF/,
// Parameters for different distances
co2factor: [0.08, 0.07, 0.07, 0.07, 0.07],
ltoCycle: [15, 15, 15, 15, 15],
load: [0.6, 0.65, 0.65, 0.65, 0.65],
freight: [0.05, 0.05, 0.05, 0.05, 0.05]
}, {
name: 'Pohjois-Eurooppa',
regex: /^E/,
co2factor: [0.07, 0.06, 0.06, 0.06, 0.06],
ltoCycle: [22, 22, 22, 22, 22],
load: [0.65, 0.7, 0.75, 0.75, 0.75],
freight: [0.05, 0.1, 0.1, 0.1, 0.1]
}, {
name: 'Etelä-Eurooppa',
regex: /^L/,
co2factor: [0.07, 0.07, 0.07, 0.07, 0.07],
ltoCycle: [22, 22, 22, 22, 22],
load: [0.65, 0.7, 0.8, 0.8, 0.8],
freight: [0.05, 0.1, 0.1, 0.1, 0.1],
}, {
name: 'Euroopasta/aan',
co2factor: [0.07, 0.07, 0.08, 0.09, 0.09],
ltoCycle: [21, 24, 22, 26, 26], // 3rd value?
load: [0.7, 0.7, 0.8, 0.8, 0.8],
freight: [0.05, 0.15, 0.15, 0.15, 0.15]
}, {
name: 'Euroopan ulkopuoliset',
co2factor: [0.08, 0.07, 0.08, 0.08, 0.09], // 2th value?
ltoCycle: [21, 22, 23, 26, 26],
load: [0.7, 0.7, 0.8, 0.8, 0.8],
freight: [0.05, 0.1, 0.15, 0.2, 0.2]
}],
indirectRouteMultiplier: 1.09,
// http://co2-raportti.fi/?heading=EU:n-p%C3%A4%C3%A4st%C3%B6kaupan-arvo-laski-kolmanneksen-t%C3%A4n%C3%A4-vuonna&page=ilmastouutisia&news_id=3665
priceCO2: 7.5, // euros per tonne of CO2
};
});

View File

@@ -0,0 +1,75 @@
'use strict';
define([
'jquery',
'lodash',
'config'
], function ($, _, config) {
var airportById = function (element, callback) {
$.get('http://localhost:8000/search.php?i=' + element.val(), null, function (data) {
callback(data[0]);
});
};
var data = function (term, page) {
return {
s: term
};
};
var results = function (data, page) {
return {results: data};
};
var findArea = function (icao) {
for (var i = 0; i < config.parameters.length; ++i) {
var param = config.parameters[i];
if (param.regex && icao.match(param.regex)) {
return i;
}
}
return config.parameters.length - 2;
};
var selectArea = function (fromCode, toCode) {
var outside = config.parameters.length - 2;
if (fromCode === outside && toCode === outside) {
return config.parameters.length - 1;
}
return Math.max(fromCode, toCode);
};
var selectRange = function (dist) {
for (var i = 0; i < config.distanceRanges.length; ++i) {
if (config.distanceRanges[i].min && dist <= config.distanceRanges[i].min) {
return i;
}
}
return 0;
};
// Should select "greater of two", finland to western europe -> west europe
// west europe to south europe -> south europe
var parameters = function (from, to, range) {
var i = selectArea(findArea(from.icao), findArea(to.icao));
var p = config.parameters[i];
return {
name: p.name,
co2factor: p.co2factor[range],
ltoCycle: p.ltoCycle[range],
load: p.load[range],
freight: p.freight[range]
};
};
return {
ajax: {
url: config.api + '/search.php',
dataType: 'json',
data: data,
results: results
},
airportById: airportById,
selectRange: selectRange,
parameters: parameters
};
});

View File

@@ -0,0 +1,9 @@
'use strict';
// convert Google Maps into an AMD module
define([
'async!http://maps.google.com/maps/api/js?v=3&sensor=false'
], function() {
// return the gmaps namespace for brevity
return window.google.maps;
});

View File

@@ -0,0 +1,10 @@
'use strict';
define([
'handlebars'
], function (Handlebars) {
Handlebars.registerHelper('displayFloat', function (num, precision) {
precision = precision;
return ''+(num.toFixed(precision)).replace('.', ',');
});
});

120
app/scripts/libs/maps.js Normal file
View File

@@ -0,0 +1,120 @@
'use strict';
define([
'require',
'jquery',
'lodash',
'backbone'
], function (require, $, _, Backbone) {
var gmaps;
function glatlng(latlng) {
if (_.isArray(latlng)) {
return new gmaps.LatLng(latlng[0], latlng[1]);
} else if (latlng instanceof Backbone.Model) {
return new gmaps.LatLng(latlng.get('lat'), latlng.get('long'));
} else {
return new gmaps.LatLng(latlng.lat, latlng.long);
}
}
var maps = {};
var deferred = $.Deferred();
var i = 0;
var Map = function (el, opts) {
var num = i;
deferred.done(function () {
maps[num] = new gmaps.Map(el, {
center: glatlng([0, 0]),
mapTypeId: gmaps.MapTypeId.ROADMAP,
zoom: 0,
streetViewControl: false,
mapTypeControl: false,
draggable: false,
scrollwheel: false,
zoomControl: false
});
});
return {
id: i++
}
};
var Line = function (attr) {
var data;
deferred.done(function () {
attr = attr || {};
attr.map = maps[attr.map.id];
data = new gmaps.Polyline(attr);
});
return {
clear: function () {
deferred.done(function () {
data.setPath([]);
});
},
add: function (latlng) {
deferred.done(function () {
data.getPath().push(glatlng(latlng));
});
}
};
};
var Bounds = function (map) {
var map;
var i;
var data;
var reset = function () {
i = 0;
map.setZoom(1);
map.setCenter(glatlng([0, 0]));
};
deferred.done(function () {
data = new gmaps.LatLngBounds();
map = maps[map.id];
reset();
});
return {
clear: function () {
deferred.done(function () {
reset();
});
},
add: function (latlng) {
deferred.done(function () {
data.extend(glatlng(latlng));
++i;
});
},
use: function () {
deferred.done(function () {
if (i >= 2) {
map.fitBounds(data);
}
});
}
};
};
require(['libs/gmaps'], function (_gmaps) {
gmaps = _gmaps;
deferred.resolve();
});
return {
Map: Map,
Line: Line,
Bounds: Bounds
};
});

26
app/scripts/libs/math.js Normal file
View File

@@ -0,0 +1,26 @@
'use strict';
define([
'config',
'libs/airports'
], function (cfg, airports) {
var toRad = function toRad(n) {
return n * Math.PI / 180;
};
var haversine = function haversine(from, to) {
var dLat = toRad(to.get('lat') - from.get('lat'));
var dLon = toRad(to.get('long') - from.get('long'));
var lat1 = toRad(from.get('lat'));
var lat2 = toRad(to.get('lat'));
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return cfg.R * c;
};
return {
toRad: toRad,
haversine: haversine
};
});

58
app/scripts/main.js Normal file
View File

@@ -0,0 +1,58 @@
'use strict';
require.config({
map: {
'*': {
'underscore': 'lodash'
}
},
paths: {
jquery: '../components/jquery/jquery',
bootstrap: '../scripts/bootstrap',
select2: '../components/select2/select2',
lodash: '../components/lodash/lodash',
handlebars: '../components/handlebars/handlebars.runtime',
Template: '../scripts/templates',
'jquery-ui-core': '../components/jquery-ui/ui/jquery.ui.core',
'jquery-ui-mouse': '../components/jquery-ui/ui/jquery.ui.mouse',
'jquery-ui-widget': '../components/jquery-ui/ui/jquery.ui.widget',
'jquery-ui-sortable': '../components/jquery-ui/ui/jquery.ui.sortable',
async: '../components/requirejs-plugins/src/async',
backbone: '../components/backbone/backbone',
'backbone-mediator': '../components/Backbone-Mediator/backbone-mediator'
},
shim: {
backbone: {
deps: ['lodash'],
exports: 'Backbone'
},
bootstrap: {
deps: ['jquery'],
exports: 'jquery'
},
select2: {
deps: ['jquery'],
exports: 'jquery'
},
'jquery-ui-core': {
deps: ['jquery'],
exports: 'jquery'
},
'jquery-ui-sortable': {
deps: ['jquery-ui-core', 'jquery-ui-widget', 'jquery-ui-mouse'],
exports: 'jquery'
},
handlebars: {
exports: 'Handlebars'
}
}
});
require([
'jquery',
'app',
'bootstrap',
'libs/handlebar_helpers'
], function ($, App) {
var app = new App();
});

20
app/scripts/models/leg.js Normal file
View File

@@ -0,0 +1,20 @@
define([
'backbone'
], function (Backbone) {
var num = 1;
var Leg = Backbone.Model.extend({
initialize: function () {
this.set('airportId', this.get('id'));
this.set('id', num);
++num;
},
destroy: function () {
this.trigger('destroy', this, this.collection, {});
}
});
return Leg;
});

View File

@@ -0,0 +1,45 @@
'use strict';
define([
'backbone',
'backbone-mediator',
'libs/math',
'libs/airports',
'config'
], function (Backbone, BackboneMediator, Math, airports, config) {
var Result = Backbone.Model.extend({
defaults: {
dist: 0,
total: 0,
lto: 0,
co2factor: 0,
ltoCycle: 0,
load: 0,
freight: 0
},
initialize: function (attr, options) {
if (this.get('from') && this.get('to')) {
this.set('dist', Math.haversine(this.get('from'), this.get('to')));
}
this.calculate.apply(this);
},
calculate: function () {
var roundtrip = 0;
if (this.get('from') && this.get('to')) {
var roundtripFactor = (roundtrip) ? 2.0 : 1.0; // fuu
this.set('range', airports.selectRange(this.get('dist')));
this.set(airports.parameters(this.get('from').toJSON(), this.get('to').toJSON(), this.get('range')));
var dist = this.get('dist') * config.indirectRouteMultiplier;
var m = config.radiativeForceFactor(dist) * (1 - this.get('freight')) * (1 / this.get('load'));
this.set('lto', m * this.get('ltoCycle'));
this.set('total', m * this.get('co2factor') * dist + this.get('lto'));
}
}
});
return Result;
});

View File

@@ -0,0 +1,54 @@
'use strict';
define([
'backbone',
'backbone-mediator',
'libs/math',
'libs/airports',
'config'
], function (Backbone, BackboneMediator, Math, airports, config) {
var Result = Backbone.Model.extend({
defaults: {
dist: 0,
total: 0,
rawTotal: 0,
alone: true,
roundtrip: false,
passengers: 1,
price: 0
},
initialize: function () {
Backbone.Mediator.subscribe('roundtrip:change', this.set.bind(this, 'roundtrip'));
Backbone.Mediator.subscribe('passengers:change', this.set.bind(this, 'passengers'));
Backbone.Mediator.subscribe('passengers:change', function (num) {
this.set('alone', num === 1);
}.bind(this));
this.on('change:roundtrip change:passengers', this.calc, this);
},
reset: function () {
this.set('dist', 0);
this.set('total', 0);
this.set('rawTotal', 0);
this.set('price', 0);
},
calc: function () {
var mult = this.get('roundtrip') ? 2 : 1;
this.set('total', mult * this.get('passengers') * this.get('rawTotal'));
this.set('price', this.get('total') / 1000 * config.priceCO2);
},
add: function (another) {
var keys = {
'dist': 'dist',
'total': 'rawTotal'
};
for (var i in keys) {
this.set(keys[i], this.get(keys[i]) + another.get(i));
}
this.calc();
}
});
return Result;
});

View File

@@ -0,0 +1,22 @@
define([
'backbone',
'select2'
], function (Backbone) {
var Dropdown = Backbone.View.extend({
events: {
'change': 'change'
},
initialize: function (opts) {
this.$el.select2(opts.select2);
},
open: function () {
this.$el.select2('open');
},
change: function () {
this.trigger('new-leg', this.$el.select2('data'));
this.$el.select2('data', {});
}
});
return Dropdown;
});

29
app/scripts/views/leg.js Normal file
View File

@@ -0,0 +1,29 @@
'use strict';
define([
'backbone',
'Template'
], function (Backbone, Template) {
var LegView = Backbone.View.extend({
tagName: 'div',
className: 'leg',
events: {
'click a.delete': 'destroy'
},
initialize: function () {
this.model.on('change', this.render, this);
this.model.on('destroy', this.remove, this);
},
destroy: function () {
this.model.destroy();
},
render: function () {
this.$el.data('id', this.model.get('id'));
this.$el.attr('id', 'leg-' + this.model.get('id')); // fuu
this.$el.html(Template.leg(this.model.toJSON()));
return this;
}
});
return LegView;
});

View File

@@ -0,0 +1,39 @@
'use strict';
define([
'backbone',
'models/leg',
'views/dropdown',
'libs/airports',
'Template'
], function (Backbone, Leg, Dropdown, airports, Template) {
var LegInput = Backbone.View.extend({
el: '.legInputWidget',
events: {
'click button': 'openDropdown'
},
initialize: function () {
this.dropdown = new Dropdown({
el: this.$el.find('input'),
select2: {
initSelection: airports.airportById,
formatResult: Template.choice,
formatSelection: Template.choice,
minimumInputLength: 1,
ajax: airports.ajax
}
});
this.dropdown.bind('new-leg', this.newLeg, this);
},
openDropdown: function () {
this.dropdown.open();
},
newLeg: function (data) {
this.collection.push(new Leg(data));
}
});
return LegInput;
});

41
app/scripts/views/map.js Normal file
View File

@@ -0,0 +1,41 @@
'use strict';
define([
'backbone',
'libs/maps'
], function (Backbone, Maps) {
var MapView = Backbone.View.extend({
el: '#gmap',
initialize: function () {
this.collection.on('sort', this.render, this);
this.collection.on('remove', this.render, this);
this.collection.on('add', this.add, this);
this.map = new Maps.Map(this.el);
this.route = new Maps.Line({
geodesic: true,
map: this.map,
strokeColor: '#000'
});
this.bounds = new Maps.Bounds(this.map);
},
render: function () {
this.route.clear();
this.bounds.clear();
this.collection.forEach(this.route.add);
this.collection.forEach(this.bounds.add);
this.bounds.use();
},
add: function (model, collection, options) {
this.route.add(model);
this.bounds.add(model);
this.bounds.use();
}
});
return MapView;
});

View File

@@ -0,0 +1,22 @@
'use strict';
define([
'backbone',
'Template',
'config',
'bootstrap'
], function (Backbone, Template, config) {
var Operation = Backbone.View.extend({
el: '#operation',
initialize: function () {
this.render.apply(this);
},
render: function () {
this.$el.html(Template.operation(config));
this.$el.find('abbr').tooltip();
return this;
}
});
return Operation;
});

View File

@@ -0,0 +1,28 @@
define([
'backbone',
'backbone-mediator'
], function (Backbone) {
var PassengersInput = Backbone.View.extend({
el: '.passengersWidget',
events: {
'change input': 'change',
'keyup input': 'change'
},
initialize: function () {
this.value = 1;
},
change: function (event) {
var val = this.$el.find('input').val();
if (!val) {
val = 1;
}
val = Number(val);
if (val !== this.value) {
this.value = val;
Backbone.Mediator.publish('passengers:change', this.value);
}
}
});
return PassengersInput;
});

View File

@@ -0,0 +1,22 @@
'use strict';
define([
'backbone',
'backbone-mediator'
], function (Backbone) {
var RoundtripInput = Backbone.View.extend({
el: '.roundtripWidget',
events: {
'click button': 'click'
},
click: function (event, el) {
this.$el.find('button').removeClass('active');
var current = this.$el.find(event.currentTarget);
current.addClass('active');
Backbone.Mediator.publish('roundtrip:change', current.hasClass('roundtrip'));
}
});
return RoundtripInput;
});

View File

@@ -0,0 +1,49 @@
'use strict';
define([
'backbone',
'views/leg',
'jquery-ui-sortable'
], function (Backbone, LegView) {
var RouteView = Backbone.View.extend({
el: '.route',
events: {
'sortstart': 'sortStart',
'sortstop': 'sortStop'
},
initialize: function () {
this.collection.bind('add', this.add, this);
this.collection.bind('sort', this.render, this);
this.$el.sortable({
handle: 'div > div',
scroll: false,
});
},
sortStart: function () {
// fadeOut results
},
sortStop: function (event, ui) {
this.updateSort();
// fadeIn results
},
updateSort: function () {
// jQuery UI already updates DOM order,
// so we only have to update Backbone collection to match that
var self = this;
this.$el.find('.leg').each(function (index) {
// Get id of model from element
var id = $(this).data('id');
self.collection.get(id).set('order', index);
});
this.collection.sort();
},
add: function (model, collection, options) {
var item = new LegView({model: model});
this.$el.append(item.render().el);
}
});
return RouteView;
});

View File

@@ -0,0 +1,19 @@
'use strict';
define([
'backbone',
'Template'
], function (Backbone, Template) {
var TotalView = Backbone.View.extend({
el: '.totalWidget',
initialize: function () {
this.render.apply(this);
this.model.bind('change', this.render, this);
},
render: function () {
this.$el.html(Template.total(this.model.toJSON()));
}
});
return TotalView;
});

141
app/styles/main.less Normal file
View File

@@ -0,0 +1,141 @@
@import 'bootstrap/less/bootstrap';
@import 'font-awesome/build/assets/font-awesome/less/font-awesome';
@import (less) 'select2/select2.css';
.calculator {
.inactive {
opacity: 0.5;
}
.nav-tabs {
a {
font-size: 21px;
}
}
}
.select2-container {
display: block;
.select2-choice {
display: none;
}
}
.select2-result {
abbr {
display: block;
width: 3em;
float: left;
color: #1657A1;
}
}
.select2-drop {
border-top: 1px solid #aaa;
.select2-search input {
margin-top: 4px;
}
&.select2-drop-above {
border-bottom: 1px solid #aaa;
}
}
.leg {
text-align: left;
position: relative;
border-bottom: 1px solid #e5e5e5;
padding: 10px 40px 10px 0;
margin: 0 0;
& > div {
& > div {
padding-left: 24px;
}
}
i.icon-sort, .delete {
position: absolute;
top: 50%;
margin-top: -7px;
float: right;
}
i.icon-sort {
left: 0;
}
.delete {
right: 0;
margin-top: -15px;
}
h2 {
font-size: 18px;
margin: 0;
line-height: 22px;
}
abbr {
float: right;
font-weight: normal;
}
i {
margin-top: -7px;
}
}
.route {
position: relative;
.leg {
position: relative;
left: 0 !important;
&:last-child {
border-bottom: 0;
}
}
}
.roundtripWidget {
.btn-group {
width: 100%;
}
button {
width: 50%;
}
}
.resultsWidget {
position: relative;
}
.legInputWidget {
.btn {
text-align: left;
span {
padding-left: 12px;
}
}
}
.totalWidget {
margin: 20px 0 0 0;
padding: 20px 0;
border-top: 1px solid #e5e5e5;
font-size: 28px;
font-weight: bold;
line-height: 28px;
text-align: center;
.distance {
display: block;
}
.raw {
font-size: 20px;
line-height: 20px;
}
.co2 {
font-size: 48px;
line-height: 48px;
}
}
input[type=number] {
text-align: right;
}
.map {
height: 300px;
}

1
app/templates/choice.hbs Normal file
View File

@@ -0,0 +1 @@
{{name}}<abbr>{{codes}}</abbr>

13
app/templates/leg.hbs Normal file
View File

@@ -0,0 +1,13 @@
<div>
<div>
<i class="icon-sort"></i>
<span class="">
<h2>{{name}}</h2>
<span class="city">{{city}}, </span>
<span class="country">{{country}}</span>
</span>
</div>
<a href="#" class="btn btn-danger delete">
<i class="icon-remove"></i>
</a>
</div>

View File

@@ -0,0 +1,58 @@
<p>
Laskuri noutaa tietokannasta, jonka lähteenä on <a href="http://openflights.org/">http://openflights.org/</a>, lentokenttien sijainnit.
Etäisyys kahden koordinaatin välillä lasketaan <a href="http://en.wikipedia.org/wiki/Haversine_formula">Haversinen kaavalla</a>.
</p>
<p>
Lähtö- ja kohdekentälle valitaan lentoalue niiden <abbr title="International Civil Aviation Organization">ICAO</abbr>-tunnusten perusteella.
Mikäli toinen kenttä on esimerkiksi Suomessa ja toinen Pohjois-Euroopassa, valitaan muuttujat jälkimmäisen mukaan.
</p>
<ul>
<li><strong>Säteilypakotekerroin</strong> <strong>1,0</strong> tai matkan ollessa yli 500km <strong>2,0</strong>.</li>
<li><strong>Epäsuoramatkakerroin</strong> koska reitit eivät kulje suorinta mahdollista reittiä, kerrotaan matka kertoimella <strong>{{displayFloat indirectRouteMultiplier 2}}</strong>.
<li><strong>CO<sub>2</sub> päästötonnin hinta</strong> {{displayFloat priceCO2 2}}€</li>
</ul>
<p>
Muuttujista rakennetaan kerroin <em>m</em> seuraavasti <em>säteilypakotekerroin * (1 - rahtikerroin) * (1 / kuormakerroin)</em>.
Lopulliset co<sub>2</sub> päästöt koostuvat kahdesta osasta: laskeutuminen ja nousu sekä lento. Lennon
päästöt lasketaan kertomalla kuljettu etäisyys kertoimilla <em>m</em> ja <em>co2</em>. Laskeutumisen ja nousut päästöt saadaan kertomalla <em>lto</em>-muuttuja kertoimella <em>m</em>.
</p>
<table class="table table-hover">
<thead>
<tr>
<td colspan="2"><strong>Etäisyys [km]</strong></i></td>
{{#each distanceRanges}}
<td><strong>{{name}}</strong></td>
{{/each}}
</tr>
<tr>
<td><strong>Lentoalue</strong></td>
<td><strong>Muuttuja</strong></td>
<td colspan="5"></td>
</tr>
</thead>
{{#each parameters}}
<tr>
<td rowspan="4">{{name}}</td>
<td>co<sub>2</sub></td>
{{#each co2factor}}<td>{{displayFloat this 2}}</td>{{/each}}
</tr>
<tr>
<td>lto</td>
{{#each ltoCycle}}<td>{{this}}</td>{{/each}}
</tr>
<tr>
<td><abbr title="Paljonko lennoilla on matkustajia.">kuorma</abbr></td>
{{#each load}}<td>{{displayFloat this 2}}</td>{{/each}}
</tr>
<tr>
<td><abbr title="Paljonko koneet ottavat rahtia.">rahti</abbr></td>
{{#each freight}}<td>{{displayFloat this 2}}</td>{{/each}}
</tr>
{{/each}}
</table>

1
app/templates/result.hbs Normal file
View File

@@ -0,0 +1 @@
<span class="distance">{{displayFloat dist 2}} km</span><span class="co2"><abbr title="lto: {{displayFloat lto 2}}">{{displayFloat total 2}} Kg CO²</abbr></span>

15
app/templates/total.hbs Normal file
View File

@@ -0,0 +1,15 @@
<p class="distance">Matkaa yhteensä {{displayFloat dist 1}} <em>km</em>.</p>
<p class="raw">{{#if alone}}
{{#if roundtrip}}
Päästöjä per suunta {{displayFloat rawTotal 0}} <em>Kg CO<sub>2</sub></em>.
{{/if}}
{{else}}
{{#if roundtrip}}
Päästöjä per henkilö per suunta {{displayFloat rawTotal 0}} <em>Kg CO²</em>.
{{else}}
Päästöjä per henkilö {{displayFloat rawTotal 0}} <em>Kg CO<sub>2</sub></em>.
{{/if}}
{{/if}}</p>
<p class="co2">{{displayFloat total 0}} <em>kg CO<sub>2</sub></em></p>
<p class="price">{{displayFloat price 2}} <em>€</em></p>

18
component.json Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "lentolaskuri2",
"version": "0.0.0",
"dependencies": {
"backbone": "git://github.com/documentcloud/backbone.git#1.0.0",
"bootstrap": "git://github.com/twitter/bootstrap.git#2.3.1",
"requirejs": "git://github.com/jrburke/requirejs#2.1.5",
"jquery": "git://github.com/components/jquery.git#1.9.1",
"select2": "git://github.com/ivaynberg/select2.git#~3.3.1",
"lodash": "git://github.com/bestiejs/lodash.git#1.0.1",
"font-awesome": "~3.1.1",
"handlebars": "git://github.com/components/handlebars.js#1.0.0-rc.3",
"jquery-ui": "git://github.com/components/jqueryui#1.10.1",
"requirejs-plugins": "git://github.com/millermedeiros/requirejs-plugins.git",
"Backbone-Mediator": "latest",
"i18next": "~1.6.0"
}
}

View File

@@ -0,0 +1,18 @@
{
"folders":
[
{
"path": "/home/juho/Source/tyo/lentolaskuri2",
"folder_exclude_patterns": ["node_modules", ".cache", ".tmp", "app/components"]
}
],
"ternjs": {
"exclude": ["node_modules/**", "app/components/**"],
"libs": ["jquery"],
"plugins": {
"requirejs": {
"baseURL": "app/components"
}
}
}
}

31
package.json Normal file
View File

@@ -0,0 +1,31 @@
{
"name": "lentolaskuri2",
"version": "0.0.0",
"dependencies": {},
"devDependencies": {
"grunt": "~0.4.1",
"grunt-contrib-copy": "~0.4.0",
"grunt-contrib-concat": "~0.1.2",
"grunt-contrib-coffee": "~0.4.0",
"grunt-contrib-uglify": "~0.1.1",
"grunt-contrib-less": "~0.5.0",
"grunt-contrib-jshint": "~0.1.1",
"grunt-contrib-cssmin": "~0.4.1",
"grunt-contrib-connect": "0.1.2",
"grunt-contrib-clean": "0.4.0",
"grunt-contrib-htmlmin": "0.1.1",
"grunt-contrib-imagemin": "0.1.2",
"grunt-contrib-livereload": "0.1.1",
"grunt-bower-hooks": "~0.2.0",
"grunt-usemin": "~0.1.9",
"grunt-regarde": "~0.1.1",
"grunt-requirejs": "~0.3.1",
"grunt-mocha": "~0.2.2",
"grunt-open": "~0.2.0",
"matchdep": "~0.1.1",
"grunt-contrib-handlebars": "~0.5.7"
},
"engines": {
"node": ">=0.8.0"
}
}