├── .editorconfig ├── .gitignore ├── .jshintrc ├── Gruntfile.js ├── LICENSE.txt ├── README.md ├── bower.json ├── bower_components └── normalize-css │ ├── .bower.json │ ├── LICENSE.md │ ├── README.md │ ├── bower.json │ └── normalize.css ├── dist ├── images │ ├── button-up-down-arrow.png │ ├── down-arrows.png │ ├── icon-link.png │ ├── icon-share.png │ ├── popup.png │ ├── tab-left.png │ ├── tab-right.png │ └── up-arrows.png ├── jquery-vertical-timeline.css ├── jquery-vertical-timeline.js ├── jquery-vertical-timeline.libs.js ├── jquery-vertical-timeline.min.css └── jquery-vertical-timeline.min.js ├── example ├── example.js ├── example.json └── style.css ├── images ├── button-up-down-arrow.png ├── down-arrows.png ├── icon-link.png ├── icon-share.png ├── popup.png ├── tab-left.png ├── tab-right.png └── up-arrows.png ├── index.html ├── js └── jquery-vertical-timeline.js ├── package.json └── styles └── styles.scss /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | components/ 3 | bower_components/* 4 | !dist 5 | 6 | # Include some parts for example 7 | !bower_components/normalize-css/ 8 | 9 | # General git ignores 10 | .sass-cache 11 | .tmp 12 | .DS_Store 13 | *.psd 14 | *.[oa] 15 | *.py[co] 16 | *.rbc 17 | *.egg 18 | *.egg-info 19 | develop-eggs 20 | .installed.cfg 21 | .Python 22 | pip-log.txt 23 | .coverage 24 | .tox 25 | *.pbxuser 26 | *.mode1v3 27 | *.mode2v3 28 | *.perspectivev3 29 | *.xcuserstate 30 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "regexp": true, 15 | "undef": true, 16 | "unused": true, 17 | "strict": true, 18 | "trailing": true, 19 | "smarttabs": true, 20 | "white": true 21 | } 22 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* global module:false */ 2 | 3 | /** 4 | * Grunt file that handles managing tasks such as rendering 5 | * SASS, providing a basic HTTP server, building a 6 | * distribution, and deploying. 7 | */ 8 | module.exports = function(grunt) { 9 | var _ = grunt.util._; 10 | 11 | // Project configuration. Many values are directly read from 12 | // package.json. 13 | grunt.initConfig({ 14 | pkg: grunt.file.readJSON('package.json'), 15 | meta: { 16 | banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + 17 | '<%= grunt.template.today("yyyy-mm-dd") + "\\n" %>' + 18 | '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' + 19 | '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + 20 | ' Licensed <%= pkg.license || _.pluck(pkg.licenses, "type").join(", ") %> */' + 21 | '<%= "\\n\\n" %>' 22 | }, 23 | 24 | // Clean up the distribution fold 25 | clean: { 26 | folder: 'dist/' 27 | }, 28 | 29 | // JS Hint checks code for coding styles and possible errors 30 | jshint: { 31 | options: { 32 | curly: true, 33 | es3: true, 34 | forin: true, 35 | latedef: true, 36 | //maxlen: 80, 37 | indent: 2, 38 | multistr: true 39 | }, 40 | files: ['Gruntfile.js', 'js/*.js'] 41 | }, 42 | 43 | // Compass is an extended SASS. Set it up so that it generates to .tmp/ 44 | compass: { 45 | options: { 46 | sassDir: 'styles', 47 | cssDir: '.tmp/css', 48 | generatedImagesDir: '.tmp/images', 49 | fontsDir: 'styles/fonts', 50 | imagesDir: 'images', 51 | javascriptsDir: 'js', 52 | importPath: 'bower_components', 53 | httpPath: './', 54 | relativeAssets: true 55 | }, 56 | dist: { 57 | options: { 58 | environment: 'production', 59 | outputStyle: 'expanded', 60 | cssDir: '.tmp/css' 61 | } 62 | } 63 | }, 64 | 65 | // Copy relevant files over to distribution 66 | copy: { 67 | images: { 68 | files: [ 69 | { 70 | cwd: './images/', 71 | expand: true, 72 | src: ['**'], 73 | dest: 'dist/images/' 74 | } 75 | ] 76 | } 77 | }, 78 | 79 | // Brings files toggether 80 | concat: { 81 | options: { 82 | separator: '\r\n\r\n' 83 | }, 84 | // JS version 85 | js: { 86 | src: ['js/<%= pkg.name %>.js'], 87 | dest: 'dist/<%= pkg.name %>.js' 88 | }, 89 | // CSS 90 | css: { 91 | src: ['.tmp/css/styles.css'], 92 | dest: 'dist/<%= pkg.name %>.css' 93 | }, 94 | // Libs 95 | libs: { 96 | src: [ 97 | 'bower_components/jquery/jquery.min.js', 98 | 'bower_components/underscore/underscore-min.js', 99 | 'bower_components/tabletop/src/tabletop.js', 100 | 'bower_components/momentjs/min/moment.min.js', 101 | 'bower_components/isotope/jquery.isotope.min.js', 102 | 'bower_components/eventEmitter/eventEmitter.min.js', 103 | 'bower_components/eventie/eventie.js', 104 | 'bower_components/imagesloaded/imagesloaded.js' 105 | ], 106 | dest: 'dist/<%= pkg.name %>.libs.js' 107 | } 108 | }, 109 | 110 | // Replace some stuff 111 | replace: { 112 | imagePath: { 113 | src: ['dist/<%= pkg.name %>.css'], 114 | dest: 'dist/<%= pkg.name %>.css', 115 | replacements: [{ 116 | from: "url('../../images", 117 | to: "url('./images" 118 | }] 119 | } 120 | }, 121 | 122 | // Minify JS for network efficiency 123 | uglify: { 124 | options: { 125 | banner: '<%= meta.banner %>' 126 | }, 127 | dist: { 128 | src: ['dist/<%= pkg.name %>.js'], 129 | dest: 'dist/<%= pkg.name %>.min.js' 130 | } 131 | }, 132 | 133 | // Minify CSS for network efficiency 134 | cssmin: { 135 | options: { 136 | banner: '<%= meta.banner %>', 137 | report: true 138 | }, 139 | css: { 140 | src: ['dist/<%= pkg.name %>.css'], 141 | dest: 'dist/<%= pkg.name %>.min.css' 142 | } 143 | }, 144 | 145 | // HTTP Server 146 | connect: { 147 | server: { 148 | options: { 149 | port: 8888 150 | } 151 | } 152 | }, 153 | 154 | // Watches files for changes and performs task 155 | watch: { 156 | files: ['<%= jshint.files %>', 'styles/*.scss'], 157 | tasks: 'watcher' 158 | } 159 | }); 160 | 161 | // Load plugin tasks 162 | grunt.loadNpmTasks('grunt-contrib-concat'); 163 | grunt.loadNpmTasks('grunt-contrib-jshint'); 164 | grunt.loadNpmTasks('grunt-contrib-compass'); 165 | grunt.loadNpmTasks('grunt-contrib-uglify'); 166 | grunt.loadNpmTasks('grunt-contrib-copy'); 167 | grunt.loadNpmTasks('grunt-contrib-clean'); 168 | grunt.loadNpmTasks('grunt-contrib-watch'); 169 | grunt.loadNpmTasks('grunt-contrib-connect'); 170 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 171 | grunt.loadNpmTasks('grunt-text-replace'); 172 | 173 | 174 | // Default build task 175 | grunt.registerTask('default', ['jshint', 'compass:dist', 'clean', 'copy', 'concat', 'replace', 'cssmin', 'uglify']); 176 | 177 | // Server and watch 178 | grunt.registerTask('watcher', ['default']); 179 | grunt.registerTask('server', ['connect', 'watch']); 180 | }; 181 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Unless otherwise noted, this software is provided under the following MIT License (text provided by http://opensource.org/licenses/MIT) 2 | 3 | Copyright (c) MinnPost and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jQuery Vertical Timeline 2 | 3 | Forked from the [Super Awesome Vertical Timeline](https://github.com/balancemedia/Timeline). A [running example can be found here](http://minnpost.github.com/jquery-vertical-timeline/). Please see the Credits below for some restrictions on use. 4 | 5 | ## How to Use 6 | 7 | ### Data 8 | 9 | Create a Google Spreadsheet with the following columns (see options for different names) and publish it. An example can be found [here](https://docs.google.com/spreadsheet/ccc?key=0AsmHVq28GtVJdG1fX3dsQlZrY18zTVA2ZG8wTXdtNHc#gid=0); 10 | 11 | * `title` - Title of the post. 12 | * `title icon` - URL to an icon to use for the title. Should be a 20x20px image. 13 | * `date` - Needs to be in one of the following formats: _'MMM DD, YYYY', 'MM/DD/YYYY', 'M/D/YYYY', 'DD MMM YYYY'_. A different parsing format can be used with the `dateParse` option. 14 | * `display date` - How to display the date (i.e. Jan 1). This will appear in the small box in the spine and should be very short. 15 | * `photo url` - URL to a photo. 16 | * `caption` - Caption for the photo. 17 | * `body` - Body text. 18 | * `read more url` - URL for the _read more_ link. 19 | 20 | #### Google data access issue (PLEASE READ) 21 | 22 | See the issue how [Tabletop.js accesses Google Spreadsheets](https://github.com/jsoma/tabletop#okay-wait-weve-got-a-big-problem-but-a-solution-too). Basically you have to set up some sort of proxy for the data as some users will have problems otherwise. 23 | 24 | ### Install the library 25 | 26 | It is easiest to install with [bower](http://bower.io/). 27 | 28 | bower install jquery-vertical-timeline 29 | 30 | This will install the dependencies as well: [jQuery](http://jquery.com/), [Underscore](http://underscorejs.org/), [TabletopJS](https://github.com/jsoma/tabletop) (this should actually be optional), [MomentJS](http://momentjs.com/), [Isotope](http://isotope.metafizzy.co/), [ImagesLoaded](https://github.com/desandro/imagesloaded) 31 | 32 | Do note that [Isotope's license](http://isotope.metafizzy.co/docs/license.html) is not clear and you may have to buy a license to use it. 33 | 34 | ### Include CSS and JS 35 | 36 | Include the CSS: 37 | 38 | 39 | 40 | #### Use JS directly 41 | 42 | Include the Javascript (dependencies) and library. 43 | 44 | 45 | 46 | 47 | #### Use via RequireJS 48 | 49 | See the [example.js](https://github.com/MinnPost/jquery-vertical-timeline/blob/master/example/example.js) to see an example. 50 | 51 | #### Use via Browserify 52 | 53 | This should work if you can get all the dependencies to work as well. 54 | 55 | ### Use 56 | 57 | First, include a container for the timeline: 58 | 59 |
60 |
61 | 62 | Call timeline with options. Note that the `key` is the ID of the Google Spreadsheet, and the `sheetname` is the name of the sheet. 63 | 64 | $('.timeline-jquery-example-1').verticalTimeline({ 65 | key: '0AsmHVq28GtVJdG1fX3dsQlZrY18zTVA2ZG8wTXdtNHc', 66 | sheetName: 'Posts' 67 | }); 68 | 69 | You can also use JSON data directly. See options below. 70 | 71 | ## Options 72 | 73 | The following options can be passed to the plugin when called: 74 | 75 | * `key`: This is the ID of the Google Spreadsheet. 76 | * Data type: string 77 | * Default value: `0AsmHVq28GtVJdG1fX3dsQlZrY18zTVA2ZG8wTXdtNHc` 78 | * `sheetName`: This is name of the sheet in the Google Spreadsheet. 79 | * Data type: string 80 | * Default value: `Posts` 81 | * `dateParse`: A [moment.js parser formatting string or array](http://momentjs.com/docs/#/parsing/string-formats/) that will parse the `date` field. 82 | * Data type: string or array of strings 83 | * Default value `['MMM DD, YYYY', 'MM/DD/YYYY', 'M/D/YYYY', 'DD MMM YYYY']` 84 | * `defaultDirection`: This is default order of the timeline. 85 | * Data type: string 86 | * Allowed values: `newest`, `oldest` 87 | * Default value: `newest` 88 | * `defaultExpansion`: This is default state of the posts. 89 | * Data type: string 90 | * Allowed values: `expanded`, `collapsed` 91 | * Default value: `expanded` 92 | * `groupFunction`: The function that will handle the grouping of the timeline. There are two functions that can be called with a string, otherwise provide your own custom function. 93 | * Data type: string or function 94 | * Allowed values: function, `groupSegmentByYear`, `groupSegmentByDecade`, `groupSegmentByDay` 95 | * Default value: `groupSegmentByYear` 96 | * `sharing`: This turns off and on sharing, but currently should not be used. 97 | * Data type: boolean 98 | * Allowed values: `false`, `true` 99 | * Default value: `false` 100 | * `gutterWidth`: The distance in pixels between post bodies. 101 | * Data type: integer 102 | * Default value: `56` 103 | * `width`: The CSS-valid width of the timeline. The default is `auto` and will use the container. 104 | * Data type: string 105 | * Default value: `auto` 106 | * `handleResize`: Enables handling the resize of the timeline to adjust widths. This is a bit buggy. 107 | * Data type: boolean 108 | * Allowed values: `false`, `true` 109 | * Default value: `false` 110 | * `columnMapping`: This maps specific columns. This should be an a simple object, where the key is the value is the column expected by the timeline, and the name of the column in the spreadsheet. 111 | * Data type: object 112 | * Default value: `{ 113 | 'title': 'title', 114 | 'title_icon': 'title icon', 115 | 'date': 'date', 116 | 'display_date': 'display date', 117 | 'photo_url': 'photo url', 118 | 'caption': 'caption', 119 | 'body': 'body', 120 | 'read_more_url': 'read more url', 121 | 'title': 'title' 122 | }` 123 | * `postTemplate`: HTML template for each post. 124 | * Data type: string 125 | * Default value: (see code) 126 | * `groupMarkerTemplate`: HTML template for each group marker. 127 | * Data type: string 128 | * Default value: (see code) 129 | * `buttonTemplate`: HTML template for the top buttons. 130 | * Data type: string 131 | * Default value: (see code) 132 | * `timelineTemplate`: HTML template for the timeline and middle line. 133 | * Data type: string 134 | * Default value: (see code) 135 | * `data`: A javascript array of objects that can be substituted for getting data from a Google Spreadsheet. See the `example.json` file for an example structure of the data. 136 | * Data type: object 137 | * Default value: [none] 138 | * `tabletopOptions`: Overrided tabletop options. See [Tabletop project](https://github.com/jsoma/tabletop). 139 | * Data type: object 140 | * Default value: `{}` 141 | 142 | ## Development 143 | 144 | ### Prerequisites 145 | 146 | All commands are assumed to on the [command line](http://en.wikipedia.org/wiki/Command-line_interface), often called the Terminal, unless otherwise noted. The following will install technologies needed for the other steps and will only needed to be run once on your computer so there is a good chance you already have these technologies on your computer. 147 | 148 | 1. Install [Git](http://git-scm.com/). 149 | * On a Mac, install [Homebrew](http://brew.sh/), then do: `brew install git` 150 | 1. Install [NodeJS](http://nodejs.org/). 151 | * On a Mac, do: `brew install node` 152 | 1. Optionally, for development, install [Grunt](http://gruntjs.com/): `npm install -g grunt-cli` 153 | 1. Install [Bower](http://bower.io/): `npm install -g bower` 154 | 1. Install [Ruby](http://www.ruby-lang.org/en/downloads/), though it is probably already installed on your system. 155 | 1. Install [Bundler](http://gembundler.com/): `gem install bundler` 156 | 1. Install [Sass](http://sass-lang.com/): `gem install sass` 157 | * On a Mac do: `sudo gem install sass` 158 | 1. Install [Compass](http://compass-style.org/): `gem install compass` 159 | * On a Mac do: `sudo gem install compass` 160 | 161 | ### Get code and install packages 162 | 163 | Get the code for this project and install the necessary dependency libraries and packages. 164 | 165 | 1. Check out this code with [Git](http://git-scm.com/): `git clone https://github.com/MinnPost/jquery-vertical-timeline.git` 166 | 1. Go into the template directory: `cd jquery-vertical-timeline` 167 | 1. Install NodeJS packages: `npm install` 168 | 1. Install Bower components: `bower install` 169 | 170 | ### Running 171 | 172 | 1. Run: `grunt server` 173 | * This will run a local webserver for development and you can view the application in your web browser at [http://localhost:8888](http://localhost:8888). 174 | * The server watches for files changes and rebuilds project when needed. 175 | 176 | ### Build 177 | 178 | To build or compile all the assets together for easy and efficient deployment, do the following. It will create all the files in the `dist/` folder. 179 | 180 | 1. Run: `grunt` 181 | 182 | ## Bugs and hacks 183 | 184 | * IE7 has some styling issues. 185 | * The original sharing code did not work anymore so that is currently removed. 186 | * Please use the [issue queue](https://github.com/MinnPost/jquery-vertical-timeline/issues) to report any more bugs. 187 | * Currently, Tabletop.js extends Array so that indexOf is available. This has some implications in browsers, especially in the context of for..in loops. Because of bad code may be in your site that is not easily updatable, we are using a [custom version of Tabletop.js](https://github.com/zzolo/tabletop). See [pull request](https://github.com/jsoma/tabletop/pull/15). 188 | 189 | 190 | ## Credits 191 | 192 | [Balance Media](http://www.builtbybalance.com) for the design and coding. 193 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-vertical-timeline", 3 | "version": "0.3.0", 4 | "homepage": "https://github.com/MinnPost/jquery-vertical-timeline", 5 | "authors": [ 6 | "Alan Palazzolo " 7 | ], 8 | "description": "A vertical timeline using jQuery and Isotope.", 9 | "main": "dist/jquery-vertical-timeline.min.js", 10 | "license": "MIT", 11 | "ignore": [ 12 | "**/.*", 13 | "node_modules", 14 | "bower_components", 15 | "test", 16 | "tests" 17 | ], 18 | "dependencies": { 19 | "tabletop": "latest", 20 | "jquery": "~1.10", 21 | "imagesloaded": "~3.1.0", 22 | "isotope": "https://github.com/desandro/isotope.git#~1.5.25", 23 | "underscore": "~1.5.2", 24 | "momentjs": "~2.5.1" 25 | }, 26 | "devDependencies": { 27 | "normalize-css": "~2.1.3", 28 | "jquery-resize": "https://github.com/cowboy/jquery-resize.git", 29 | "requirejs": "~2.1.10" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /bower_components/normalize-css/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "normalize-css", 3 | "version": "2.1.3", 4 | "main": "normalize.css", 5 | "author": "Nicolas Gallagher", 6 | "ignore": [ 7 | "CHANGELOG.md", 8 | "CONTRIBUTING.md", 9 | "component.json", 10 | "test.html" 11 | ], 12 | "homepage": "https://github.com/necolas/normalize.css", 13 | "_release": "2.1.3", 14 | "_resolution": { 15 | "type": "version", 16 | "tag": "v2.1.3", 17 | "commit": "be14934fad255a01ed345de6ae59ff612adcf3b9" 18 | }, 19 | "_source": "git://github.com/necolas/normalize.css.git", 20 | "_target": "~2.1.3", 21 | "_originalSource": "normalize-css" 22 | } -------------------------------------------------------------------------------- /bower_components/normalize-css/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) Nicolas Gallagher and Jonathan Neal 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /bower_components/normalize-css/README.md: -------------------------------------------------------------------------------- 1 | # normalize.css v2 2 | 3 | Normalize.css is a customisable CSS file that makes browsers render all 4 | elements more consistently and in line with modern standards. 5 | 6 | The project relies on researching the differences between default browser 7 | styles in order to precisely target only the styles that need or benefit from 8 | normalizing. 9 | 10 | [Check out the demo](http://necolas.github.io/normalize.css/latest/test.html) 11 | 12 | ## Install 13 | 14 | Download from the [project page](http://necolas.github.io/normalize.css/). 15 | 16 | Install with [Bower](http://bower.io/): `bower install --save normalize-css` 17 | 18 | Install with [Component(1)](http://component.io/): `component install necolas/normalize.css` 19 | 20 | ## What does it do? 21 | 22 | * Preserves useful defaults, unlike many CSS resets. 23 | * Normalizes styles for a wide range of elements. 24 | * Corrects bugs and common browser inconsistencies. 25 | * Improves usability with subtle improvements. 26 | * Explains what code does using detailed comments. 27 | 28 | ## How to use it 29 | 30 | No other styles should come before Normalize.css. 31 | 32 | It is recommended that you include the `normalize.css` file as untouched 33 | library code. 34 | 35 | ## Browser support 36 | 37 | * Google Chrome 38 | * Mozilla Firefox 4+ 39 | * Apple Safari 5+ 40 | * Opera 12+ 41 | * Internet Explorer 8+ 42 | 43 | [Normalize.css v1 provides legacy browser 44 | support](https://github.com/necolas/normalize.css/tree/v1) (IE 6+, Safari 4+), 45 | but is no longer actively developed. 46 | 47 | ## Contributing 48 | 49 | Please read the CONTRIBUTING.md 50 | 51 | ## Acknowledgements 52 | 53 | Normalize.css is a project by [Nicolas Gallagher](https://github.com/necolas), 54 | co-created with [Jonathan Neal](https://github.com/jonathantneal). 55 | -------------------------------------------------------------------------------- /bower_components/normalize-css/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "normalize-css", 3 | "version": "2.1.3", 4 | "main": "normalize.css", 5 | "author": "Nicolas Gallagher", 6 | "ignore": [ 7 | "CHANGELOG.md", 8 | "CONTRIBUTING.md", 9 | "component.json", 10 | "test.html" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /bower_components/normalize-css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.1.3 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /** 8 | * Correct `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | main, 20 | nav, 21 | section, 22 | summary { 23 | display: block; 24 | } 25 | 26 | /** 27 | * Correct `inline-block` display not defined in IE 8/9. 28 | */ 29 | 30 | audio, 31 | canvas, 32 | video { 33 | display: inline-block; 34 | } 35 | 36 | /** 37 | * Prevent modern browsers from displaying `audio` without controls. 38 | * Remove excess height in iOS 5 devices. 39 | */ 40 | 41 | audio:not([controls]) { 42 | display: none; 43 | height: 0; 44 | } 45 | 46 | /** 47 | * Address `[hidden]` styling not present in IE 8/9. 48 | * Hide the `template` element in IE, Safari, and Firefox < 22. 49 | */ 50 | 51 | [hidden], 52 | template { 53 | display: none; 54 | } 55 | 56 | /* ========================================================================== 57 | Base 58 | ========================================================================== */ 59 | 60 | /** 61 | * 1. Set default font family to sans-serif. 62 | * 2. Prevent iOS text size adjust after orientation change, without disabling 63 | * user zoom. 64 | */ 65 | 66 | html { 67 | font-family: sans-serif; /* 1 */ 68 | -ms-text-size-adjust: 100%; /* 2 */ 69 | -webkit-text-size-adjust: 100%; /* 2 */ 70 | } 71 | 72 | /** 73 | * Remove default margin. 74 | */ 75 | 76 | body { 77 | margin: 0; 78 | } 79 | 80 | /* ========================================================================== 81 | Links 82 | ========================================================================== */ 83 | 84 | /** 85 | * Remove the gray background color from active links in IE 10. 86 | */ 87 | 88 | a { 89 | background: transparent; 90 | } 91 | 92 | /** 93 | * Address `outline` inconsistency between Chrome and other browsers. 94 | */ 95 | 96 | a:focus { 97 | outline: thin dotted; 98 | } 99 | 100 | /** 101 | * Improve readability when focused and also mouse hovered in all browsers. 102 | */ 103 | 104 | a:active, 105 | a:hover { 106 | outline: 0; 107 | } 108 | 109 | /* ========================================================================== 110 | Typography 111 | ========================================================================== */ 112 | 113 | /** 114 | * Address variable `h1` font-size and margin within `section` and `article` 115 | * contexts in Firefox 4+, Safari 5, and Chrome. 116 | */ 117 | 118 | h1 { 119 | font-size: 2em; 120 | margin: 0.67em 0; 121 | } 122 | 123 | /** 124 | * Address styling not present in IE 8/9, Safari 5, and Chrome. 125 | */ 126 | 127 | abbr[title] { 128 | border-bottom: 1px dotted; 129 | } 130 | 131 | /** 132 | * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 133 | */ 134 | 135 | b, 136 | strong { 137 | font-weight: bold; 138 | } 139 | 140 | /** 141 | * Address styling not present in Safari 5 and Chrome. 142 | */ 143 | 144 | dfn { 145 | font-style: italic; 146 | } 147 | 148 | /** 149 | * Address differences between Firefox and other browsers. 150 | */ 151 | 152 | hr { 153 | -moz-box-sizing: content-box; 154 | box-sizing: content-box; 155 | height: 0; 156 | } 157 | 158 | /** 159 | * Address styling not present in IE 8/9. 160 | */ 161 | 162 | mark { 163 | background: #ff0; 164 | color: #000; 165 | } 166 | 167 | /** 168 | * Correct font family set oddly in Safari 5 and Chrome. 169 | */ 170 | 171 | code, 172 | kbd, 173 | pre, 174 | samp { 175 | font-family: monospace, serif; 176 | font-size: 1em; 177 | } 178 | 179 | /** 180 | * Improve readability of pre-formatted text in all browsers. 181 | */ 182 | 183 | pre { 184 | white-space: pre-wrap; 185 | } 186 | 187 | /** 188 | * Set consistent quote types. 189 | */ 190 | 191 | q { 192 | quotes: "\201C" "\201D" "\2018" "\2019"; 193 | } 194 | 195 | /** 196 | * Address inconsistent and variable font size in all browsers. 197 | */ 198 | 199 | small { 200 | font-size: 80%; 201 | } 202 | 203 | /** 204 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 205 | */ 206 | 207 | sub, 208 | sup { 209 | font-size: 75%; 210 | line-height: 0; 211 | position: relative; 212 | vertical-align: baseline; 213 | } 214 | 215 | sup { 216 | top: -0.5em; 217 | } 218 | 219 | sub { 220 | bottom: -0.25em; 221 | } 222 | 223 | /* ========================================================================== 224 | Embedded content 225 | ========================================================================== */ 226 | 227 | /** 228 | * Remove border when inside `a` element in IE 8/9. 229 | */ 230 | 231 | img { 232 | border: 0; 233 | } 234 | 235 | /** 236 | * Correct overflow displayed oddly in IE 9. 237 | */ 238 | 239 | svg:not(:root) { 240 | overflow: hidden; 241 | } 242 | 243 | /* ========================================================================== 244 | Figures 245 | ========================================================================== */ 246 | 247 | /** 248 | * Address margin not present in IE 8/9 and Safari 5. 249 | */ 250 | 251 | figure { 252 | margin: 0; 253 | } 254 | 255 | /* ========================================================================== 256 | Forms 257 | ========================================================================== */ 258 | 259 | /** 260 | * Define consistent border, margin, and padding. 261 | */ 262 | 263 | fieldset { 264 | border: 1px solid #c0c0c0; 265 | margin: 0 2px; 266 | padding: 0.35em 0.625em 0.75em; 267 | } 268 | 269 | /** 270 | * 1. Correct `color` not being inherited in IE 8/9. 271 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 272 | */ 273 | 274 | legend { 275 | border: 0; /* 1 */ 276 | padding: 0; /* 2 */ 277 | } 278 | 279 | /** 280 | * 1. Correct font family not being inherited in all browsers. 281 | * 2. Correct font size not being inherited in all browsers. 282 | * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. 283 | */ 284 | 285 | button, 286 | input, 287 | select, 288 | textarea { 289 | font-family: inherit; /* 1 */ 290 | font-size: 100%; /* 2 */ 291 | margin: 0; /* 3 */ 292 | } 293 | 294 | /** 295 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 296 | * the UA stylesheet. 297 | */ 298 | 299 | button, 300 | input { 301 | line-height: normal; 302 | } 303 | 304 | /** 305 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 306 | * All other form control elements do not inherit `text-transform` values. 307 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. 308 | * Correct `select` style inheritance in Firefox 4+ and Opera. 309 | */ 310 | 311 | button, 312 | select { 313 | text-transform: none; 314 | } 315 | 316 | /** 317 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 318 | * and `video` controls. 319 | * 2. Correct inability to style clickable `input` types in iOS. 320 | * 3. Improve usability and consistency of cursor style between image-type 321 | * `input` and others. 322 | */ 323 | 324 | button, 325 | html input[type="button"], /* 1 */ 326 | input[type="reset"], 327 | input[type="submit"] { 328 | -webkit-appearance: button; /* 2 */ 329 | cursor: pointer; /* 3 */ 330 | } 331 | 332 | /** 333 | * Re-set default cursor for disabled elements. 334 | */ 335 | 336 | button[disabled], 337 | html input[disabled] { 338 | cursor: default; 339 | } 340 | 341 | /** 342 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 343 | * 2. Remove excess padding in IE 8/9/10. 344 | */ 345 | 346 | input[type="checkbox"], 347 | input[type="radio"] { 348 | box-sizing: border-box; /* 1 */ 349 | padding: 0; /* 2 */ 350 | } 351 | 352 | /** 353 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 354 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome 355 | * (include `-moz` to future-proof). 356 | */ 357 | 358 | input[type="search"] { 359 | -webkit-appearance: textfield; /* 1 */ 360 | -moz-box-sizing: content-box; 361 | -webkit-box-sizing: content-box; /* 2 */ 362 | box-sizing: content-box; 363 | } 364 | 365 | /** 366 | * Remove inner padding and search cancel button in Safari 5 and Chrome 367 | * on OS X. 368 | */ 369 | 370 | input[type="search"]::-webkit-search-cancel-button, 371 | input[type="search"]::-webkit-search-decoration { 372 | -webkit-appearance: none; 373 | } 374 | 375 | /** 376 | * Remove inner padding and border in Firefox 4+. 377 | */ 378 | 379 | button::-moz-focus-inner, 380 | input::-moz-focus-inner { 381 | border: 0; 382 | padding: 0; 383 | } 384 | 385 | /** 386 | * 1. Remove default vertical scrollbar in IE 8/9. 387 | * 2. Improve readability and alignment in all browsers. 388 | */ 389 | 390 | textarea { 391 | overflow: auto; /* 1 */ 392 | vertical-align: top; /* 2 */ 393 | } 394 | 395 | /* ========================================================================== 396 | Tables 397 | ========================================================================== */ 398 | 399 | /** 400 | * Remove most spacing between table cells. 401 | */ 402 | 403 | table { 404 | border-collapse: collapse; 405 | border-spacing: 0; 406 | } 407 | -------------------------------------------------------------------------------- /dist/images/button-up-down-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinnPost/jquery-vertical-timeline/609c704f4077d097fcc0e400135c644cf049b5c4/dist/images/button-up-down-arrow.png -------------------------------------------------------------------------------- /dist/images/down-arrows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinnPost/jquery-vertical-timeline/609c704f4077d097fcc0e400135c644cf049b5c4/dist/images/down-arrows.png -------------------------------------------------------------------------------- /dist/images/icon-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinnPost/jquery-vertical-timeline/609c704f4077d097fcc0e400135c644cf049b5c4/dist/images/icon-link.png -------------------------------------------------------------------------------- /dist/images/icon-share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinnPost/jquery-vertical-timeline/609c704f4077d097fcc0e400135c644cf049b5c4/dist/images/icon-share.png -------------------------------------------------------------------------------- /dist/images/popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinnPost/jquery-vertical-timeline/609c704f4077d097fcc0e400135c644cf049b5c4/dist/images/popup.png -------------------------------------------------------------------------------- /dist/images/tab-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinnPost/jquery-vertical-timeline/609c704f4077d097fcc0e400135c644cf049b5c4/dist/images/tab-left.png -------------------------------------------------------------------------------- /dist/images/tab-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinnPost/jquery-vertical-timeline/609c704f4077d097fcc0e400135c644cf049b5c4/dist/images/tab-right.png -------------------------------------------------------------------------------- /dist/images/up-arrows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinnPost/jquery-vertical-timeline/609c704f4077d097fcc0e400135c644cf049b5c4/dist/images/up-arrows.png -------------------------------------------------------------------------------- /dist/jquery-vertical-timeline.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS for timeline-jquery. 3 | */ 4 | .vertical-timeline-container { 5 | *zoom: 1; 6 | /** 7 | * Posts 8 | */ 9 | /** 10 | * Group marker 11 | */ 12 | /** 13 | * Group 14 | */ 15 | /** 16 | * Top buttons 17 | */ 18 | /** 19 | * Layout 20 | */ 21 | /** 22 | * Isotope Filtering 23 | */ 24 | /** 25 | * Isotope CSS3 transitions 26 | */ 27 | /** 28 | * disabling Isotope CSS3 transitions 29 | */ 30 | } 31 | .vertical-timeline-container:before, .vertical-timeline-container:after { 32 | content: " "; 33 | display: table; 34 | } 35 | .vertical-timeline-container:after { 36 | clear: both; 37 | } 38 | .vertical-timeline-container .vertical-timeline-timeline { 39 | width: 600px; 40 | margin: 20px auto 30px auto; 41 | display: none; 42 | } 43 | .vertical-timeline-container .clearfix { 44 | *zoom: 1; 45 | } 46 | .vertical-timeline-container .clearfix:before, .vertical-timeline-container .clearfix:after { 47 | content: " "; 48 | display: table; 49 | } 50 | .vertical-timeline-container .clearfix:after { 51 | clear: both; 52 | } 53 | .vertical-timeline-container .loading { 54 | text-align: center; 55 | font-size: 2em; 56 | margin: 1em; 57 | color: #565656; 58 | } 59 | .vertical-timeline-container .post { 60 | width: 271px; 61 | margin: 0 0 10px 0; 62 | float: left; 63 | } 64 | .vertical-timeline-container .post.last { 65 | margin-bottom: 0; 66 | } 67 | .vertical-timeline-container .post .inner { 68 | position: relative; 69 | padding: 11px; 70 | border: 1px #adadad solid; 71 | background-color: #fff; 72 | min-height: 37px; 73 | word-wrap: break-word; 74 | -moz-border-radius: 2px; 75 | -webkit-border-radius: 2px; 76 | border-radius: 2px; 77 | -moz-box-shadow: 0px 1px 1px 0px #ADADAD; 78 | -webkit-box-shadow: 0px 1px 1px 0px #ADADAD; 79 | box-shadow: 0px 1px 1px 0px #ADADAD; 80 | } 81 | .vertical-timeline-container .post.right { 82 | float: right; 83 | } 84 | .vertical-timeline-container .post .timestamp { 85 | display: none; 86 | } 87 | .vertical-timeline-container .post h3 { 88 | margin: 0; 89 | font-family: Georgia, sans-serif; 90 | font-size: 20px; 91 | font-weight: normal; 92 | color: #010101; 93 | padding-right: 30px; 94 | line-height: 1.3em; 95 | } 96 | .vertical-timeline-container .post .caption { 97 | color: #9d9d9d; 98 | font-family: Georgia, sans-serif; 99 | font-size: 12px; 100 | margin-top: 4px; 101 | } 102 | .vertical-timeline-container .post .body { 103 | margin-top: 10px; 104 | } 105 | .vertical-timeline-container .post.collapsed .body { 106 | display: none; 107 | } 108 | .vertical-timeline-container .post.expanded .body { 109 | display: block; 110 | } 111 | .vertical-timeline-container .post .body img { 112 | max-width: 100%; 113 | } 114 | .vertical-timeline-container .post .text { 115 | color: #393939; 116 | font-family: Georgia, sans-serif; 117 | font-size: 15px; 118 | margin: 10px 0; 119 | line-height: 1.5; 120 | } 121 | .vertical-timeline-container .post a.open-close { 122 | background: transparent url('./images/button-up-down-arrow.png?1471988818') no-repeat; 123 | height: 17px; 124 | width: 16px; 125 | position: absolute; 126 | right: 11px; 127 | top: 11px; 128 | } 129 | .vertical-timeline-container .post.collapsed a.open-close { 130 | background-position: left bottom; 131 | } 132 | .vertical-timeline-container .post .title .title-icon { 133 | height: 20px; 134 | width: 20px; 135 | margin-right: 4px; 136 | vertical-align: top; 137 | } 138 | .vertical-timeline-container .post.closed .title { 139 | display: table; 140 | min-height: 40px; 141 | } 142 | .vertical-timeline-container .post.closed .title h3 { 143 | display: table-cell; 144 | vertical-align: middle; 145 | } 146 | .vertical-timeline-container .post a.more { 147 | color: #676767; 148 | text-decoration: none; 149 | text-transform: uppercase; 150 | font-size: 12px; 151 | font-weight: bold; 152 | text-align: center; 153 | padding: 4px 15px; 154 | margin: 5px auto 0 auto; 155 | border: 1px #BDBDBD solid; 156 | display: block; 157 | line-height: 28px; 158 | -moz-border-radius: 2px; 159 | -webkit-border-radius: 2px; 160 | border-radius: 2px; 161 | -moz-box-shadow: 0px 1px 1px 0px #BDBDBD; 162 | -webkit-box-shadow: 0px 1px 1px 0px #BDBDBD; 163 | box-shadow: 0px 1px 1px 0px #BDBDBD; 164 | } 165 | .vertical-timeline-container .post a.more:hover { 166 | background-color: #FAFAFA; 167 | } 168 | .vertical-timeline-container .post .date { 169 | color: #6a6a6a; 170 | font-size: 11px; 171 | text-align: center; 172 | position: absolute; 173 | top: 19px; 174 | display: block; 175 | width: 54px; 176 | height: 21px; 177 | line-height: 20px; 178 | } 179 | .vertical-timeline-container .post.left .date { 180 | background: transparent url('./images/tab-left.png?1471988818') no-repeat right; 181 | right: -54px; 182 | } 183 | .vertical-timeline-container .post.right .date { 184 | background: transparent url('./images/tab-right.png?1471988818') no-repeat left; 185 | left: -54px; 186 | } 187 | .vertical-timeline-container .group-marker { 188 | float: left; 189 | width: 80px; 190 | margin: 50px 0 15px 0; 191 | } 192 | .vertical-timeline-container .group-marker .timestamp { 193 | display: none; 194 | } 195 | .vertical-timeline-container .group-marker.top { 196 | margin-top: 0; 197 | } 198 | .vertical-timeline-container .group-marker .inner { 199 | width: 76px; 200 | height: 26px; 201 | padding: 2px; 202 | background-color: #FFF; 203 | } 204 | .vertical-timeline-container .group-marker .inner2 { 205 | background-color: #434a50; 206 | -moz-border-radius: 2px; 207 | -webkit-border-radius: 2px; 208 | border-radius: 2px; 209 | } 210 | .vertical-timeline-container .group { 211 | text-align: center; 212 | color: #fff; 213 | font-size: 14px; 214 | font-weight: bold; 215 | height: 26px; 216 | line-height: 26px; 217 | } 218 | .vertical-timeline-container .vertical-timeline-buttons { 219 | text-align: center; 220 | margin: 20px 0 10px 0; 221 | } 222 | .vertical-timeline-container .vertical-timeline-buttons div { 223 | display: -moz-inline-stack; 224 | display: inline-block; 225 | zoom: 1; 226 | *display: inline; 227 | } 228 | .vertical-timeline-container .vertical-timeline-buttons .expand-collapse-buttons { 229 | margin-right: 20px; 230 | } 231 | .vertical-timeline-container .vertical-timeline-buttons a { 232 | display: -moz-inline-stack; 233 | display: inline-block; 234 | zoom: 1; 235 | *display: inline; 236 | width: 126px; 237 | border: 1px #adadad solid; 238 | -moz-border-radius: 2px; 239 | -webkit-border-radius: 2px; 240 | border-radius: 2px; 241 | font-size: 12px; 242 | font-weight: bold; 243 | text-decoration: none; 244 | padding: 10px 12px; 245 | background-color: #FFF; 246 | color: #3b3b3b; 247 | text-transform: uppercase; 248 | } 249 | .vertical-timeline-container .vertical-timeline-buttons a.active:hover, 250 | .vertical-timeline-container .vertical-timeline-buttons a.active { 251 | background-color: #f6f6f6; 252 | color: #a9a9a9; 253 | cursor: default; 254 | -moz-box-shadow: none; 255 | -webkit-box-shadow: none; 256 | box-shadow: none; 257 | } 258 | .vertical-timeline-container .vertical-timeline-buttons a:hover { 259 | -moz-box-shadow: 0px 1px 1px 0px #adadad; 260 | -webkit-box-shadow: 0px 1px 1px 0px #adadad; 261 | box-shadow: 0px 1px 1px 0px #adadad; 262 | } 263 | .vertical-timeline-container .vertical-timeline-buttons a span { 264 | padding-right: 15px; 265 | background-position: right -20px; 266 | } 267 | .vertical-timeline-container .vertical-timeline-buttons a.active span { 268 | background-position: right 4px; 269 | } 270 | .vertical-timeline-container .vertical-timeline-buttons a.expand-all span, 271 | .vertical-timeline-container .vertical-timeline-buttons a.sort-newest span { 272 | background-image: url('./images/down-arrows.png?1471988818'); 273 | background-repeat: no-repeat; 274 | } 275 | .vertical-timeline-container .vertical-timeline-buttons a.collapse-all span, 276 | .vertical-timeline-container .vertical-timeline-buttons a.sort-oldest span { 277 | background-image: url('./images/up-arrows.png?1471988818'); 278 | background-repeat: no-repeat; 279 | } 280 | @media (max-width: 675px) { 281 | .vertical-timeline-container .vertical-timeline-buttons .expand-collapse-buttons, 282 | .vertical-timeline-container .vertical-timeline-buttons .sort-buttons { 283 | margin: 0 0 10px 0; 284 | } 285 | } 286 | .vertical-timeline-container .line-container { 287 | width: 4px; 288 | text-align: center; 289 | margin: 0 auto; 290 | display: block; 291 | } 292 | .vertical-timeline-container .isotope .line { 293 | margin: 0 auto; 294 | background-color: #b3b6b8; 295 | display: block; 296 | float: left; 297 | height: 100%; 298 | left: 298px; 299 | width: 4px; 300 | position: absolute; 301 | } 302 | .vertical-timeline-container .isotope-item { 303 | z-index: 2; 304 | } 305 | .vertical-timeline-container .isotope-hidden.isotope-item { 306 | pointer-events: none; 307 | z-index: 1; 308 | } 309 | .vertical-timeline-container .vertical-timeline-container .isotope, 310 | .vertical-timeline-container .vertical-timeline-container .isotope .isotope-item { 311 | -moz-transition-duration: 0.8s; 312 | -o-transition-duration: 0.8s; 313 | -webkit-transition-duration: 0.8s; 314 | transition-duration: 0.8s; 315 | } 316 | .vertical-timeline-container .vertical-timeline-container .isotope { 317 | -moz-transition-property: height, width; 318 | -o-transition-property: height, width; 319 | -webkit-transition-property: height, width; 320 | transition-property: height, width; 321 | } 322 | .vertical-timeline-container .vertical-timeline-container .isotope .isotope-item { 323 | -webkit-transition-property: -webkit-transform, opacity; 324 | -moz-transition-property: -moz-transform, opacity; 325 | -ms-transition-property: -ms-transform, opacity; 326 | -o-transition-property: top, left, opacity; 327 | transition-property: transform, opacity; 328 | } 329 | .vertical-timeline-container .isotope.no-transition, 330 | .vertical-timeline-container .isotope.no-transition .isotope-item, 331 | .vertical-timeline-container .isotope .isotope-item.no-transition { 332 | -moz-transition-duration: 0s; 333 | -o-transition-duration: 0s; 334 | -webkit-transition-duration: 0s; 335 | transition-duration: 0s; 336 | } 337 | -------------------------------------------------------------------------------- /dist/jquery-vertical-timeline.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Vertical timeline plugin for jQuery. 3 | */ 4 | 5 | // Wrapper to handle using with RequireJS or Browserify or as a global 6 | (function(global, factory) { 7 | // Common JS (i.e. browserify) environment 8 | if (typeof module !== 'undefined' && module.exports && typeof require === 'function') { 9 | factory(require('jquery'), require('underscore'), require('tabletop'), require('isotope'), require('imagesloaded'), require('moment')); 10 | } 11 | // AMD? 12 | else if (typeof define === 'function' && define.amd) { 13 | define('jquery-vertical-timeline', ['jquery', 'underscore', 'tabletop', 'moment', 'isotope', 'imagesloaded'], factory); 14 | } 15 | // Browser global 16 | else if (global.jQuery && global._ && global.Tabletop && global.moment && global.jQuery.fn.isotope && global.jQuery.fn.imagesLoaded) { 17 | factory(global.jQuery, global._, global.Tabletop, global.moment, global.jQuery.fn.isotope, global.jQuery.fn.resize, global.jQuery.fn.imagesLoaded); 18 | } 19 | else { 20 | throw new Error('Could not find dependencies for jQuery Vertical Timeline.' ); 21 | } 22 | })(typeof window !== 'undefined' ? window : this, function($, _, Tabletop, moment) { 23 | 24 | /** 25 | * Default options 26 | */ 27 | var defaultsOptions = { 28 | key: 'https://docs.google.com/spreadsheet/pub?key=0AsmHVq28GtVJdG1fX3dsQlZrY18zTVA2ZG8wTXdtNHc&output=html', 29 | sheetName: 'Posts', 30 | dateParse: ['MMM DD, YYYY', 'MM/DD/YYYY', 'M/D/YYYY', 'DD MMM YYYY'], 31 | defaultDirection: 'newest', 32 | defaultExpansion: 'expanded', 33 | groupFunction: 'groupSegmentByYear', 34 | sharing: false, 35 | gutterWidth: 57, 36 | width: 'auto', 37 | handleResize: false, 38 | tabletopOptions: {}, 39 | columnMapping: { 40 | 'title': 'title', 41 | 'title_icon': 'title icon', 42 | 'date': 'date', 43 | 'display_date': 'display date', 44 | 'photo_url': 'photo url', 45 | 'caption': 'caption', 46 | 'body': 'body', 47 | 'read_more_url': 'read more url' 48 | }, 49 | postTemplate: '
\ 50 |
\ 51 |
\ 52 |

\ 53 | <% if (data.title_icon) { %><% } %> \ 54 | <%= data.title %> \ 55 | <\/h3> \ 56 | <\/div> \ 57 |
<%= data.display_date %><\/div> \ 58 |
\ 59 | <% if (data.photo_url) { %> \ 60 | \ 61 | <% } %> \ 62 | <% if (data.caption) { %> \ 63 |
<%= data.caption %><\/div> \ 64 | <% } %> \ 65 | <% if (data.body) { %> \ 66 |
<%= data.body %><\/div> \ 67 | <% } %> \ 68 |
\ 69 | <% if (data.read_more_url) { %> \ 70 | Read more<\/a> \ 71 | <% } %> \ 72 | <\/div> \ 73 | <\/div> \ 74 | \ 75 | <\/div> \ 76 | <\/div> \ 77 | ', 78 | groupMarkerTemplate: '
\ 79 |
\ 80 |
\ 81 |
<%= data.groupDisplay %><\/div> \ 82 | <\/div> \ 83 | <\/div> \ 84 | <\/div> \ 85 | ', 86 | buttonTemplate: '
\ 87 |
\ 88 | active<% } %>" href="#">Expand all<\/span><\/a> \ 89 | active<% } %>" href="#">Collapse all<\/span><\/a> \ 90 | <\/div> \ 91 |
\ 92 | active<% } %>" href="#">Newest first<\/span><\/a> \ 93 | active<% } %>" href="#">Oldest first<\/span><\/a> \ 94 | <\/div> \ 95 | <\/div> \ 96 | ', 97 | timelineTemplate: '
\ 98 |
\ 99 |
<\/div> \ 100 | <\/div> \ 101 | <%= data.posts %> \ 102 | <%= data.groups %> \ 103 | <\/div> \ 104 | ', 105 | loadingTemplate: '
\ 106 | Loading... \ 107 | <\/div> \ 108 | ' 109 | }; 110 | 111 | var groupingFunctions = {}; 112 | /** 113 | * Grouping function by Decade. 114 | */ 115 | groupingFunctions.groupSegmentByDecade = function(row, groups, direction) { 116 | var year = row.date.year(); 117 | var yearStr = year.toString(); 118 | var id = yearStr.slice(0, -1); 119 | var start = moment(id + '0-01-01T00:00:00'); 120 | var end = moment(id + '9-12-31T12:59:99'); 121 | 122 | if (_.isUndefined(groups[id])) { 123 | groups[id] = { 124 | id: id, 125 | groupDisplay: id + '0s', 126 | timestamp: (direction == 'newest') ? end.unix() : start.unix(), 127 | timestampStart: start.unix(), 128 | timestampEnd: end.unix() 129 | }; 130 | } 131 | 132 | return groups; 133 | }; 134 | 135 | /** 136 | * Grouping function by year. 137 | */ 138 | groupingFunctions.groupSegmentByYear = function(row, groups, direction) { 139 | var year = row.date.year(); 140 | var start = moment(year + '-01-01T00:00:00'); 141 | var end = moment(year + '-12-31T12:59:99'); 142 | 143 | if (_.isUndefined(groups[year.toString()])) { 144 | groups[year.toString()] = { 145 | id: year, 146 | groupDisplay: year, 147 | timestamp: (direction == 'newest') ? end.unix() : start.unix(), 148 | timestampStart: start.unix(), 149 | timestampEnd: end.unix() 150 | }; 151 | } 152 | 153 | return groups; 154 | }; 155 | 156 | /** 157 | * Grouping function by day. 158 | */ 159 | groupingFunctions.groupSegmentByDay = function(row, groups, direction) { 160 | var month = new Date(row.timestamp).getMonth(); 161 | var year = new Date(row.timestamp).getFullYear(); 162 | var day = new Date(row.timestamp).getDate(); 163 | var _month_str = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'July', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec']; 164 | var _time_start = Date.parse(_month_str[month] + ' ' + day + ', ' + year); 165 | var _time_end = Date.parse(_month_str[month] + ' ' + (day+1) + ', ' + year); 166 | var _id = day + (month + year * 100) * 100; 167 | 168 | groups[_id] = { 169 | id: _id, 170 | groupDisplay: _month_str[month] + ' ' + day + ', ' + year, 171 | timestamp: (direction == 'newest') ? _time_end: _time_start, 172 | timestampStart: _time_start, 173 | timestampEnd: _time_end 174 | }; 175 | 176 | return groups; 177 | }; 178 | 179 | 180 | 181 | /** 182 | * Base class for timeline 183 | */ 184 | var VerticalTimeline = function(el, options) { 185 | this.options = $.extend(true, {}, defaultsOptions, options); 186 | 187 | // Check to see if grouping function is an option 188 | this.options.groupFunction = (!_.isFunction(this.options.groupFunction) && _.isFunction(groupingFunctions[this.options.groupFunction])) ? groupingFunctions[this.options.groupFunction] : this.options.groupFunction; 189 | 190 | // Consistent reference to jquery object 191 | this.$el = $(el); 192 | 193 | // Build templates for performance 194 | this.templates = {}; 195 | this.templates.post = _.template(this.options.postTemplate); 196 | this.templates.group = _.template(this.options.groupMarkerTemplate); 197 | this.templates.buttons = _.template(this.options.buttonTemplate); 198 | this.templates.timeline = _.template(this.options.timelineTemplate); 199 | this.templates.loading = _.template(this.options.loadingTemplate); 200 | 201 | // Use custom events to make things happens 202 | this.loadEvents(); 203 | this.$el.trigger('vt.build'); 204 | }; 205 | 206 | /** 207 | * Methods and properties of class 208 | */ 209 | _.extend(VerticalTimeline.prototype, { 210 | // Events 211 | events: { 212 | 'vt.build': ['buildLayout'], 213 | 'vt.layoutBuilt': ['getData'], 214 | 'vt.gotData': ['parseData'], 215 | 'vt.parsedData': ['buildTimeline'], 216 | 'vt.builtTimeline': ['loadImages', 'adjustWidth'], 217 | 'vt.loadedImages': ['isotopeIt'], 218 | 'vt.isotopized': ['adjustWidth', 'adjustSpine', 'domEvents'] 219 | }, 220 | 221 | // Event delegation 222 | loadEvents: function() { 223 | _.each(this.events, function(ea, ename) { 224 | _.each(ea, function(ehandler) { 225 | if (_.isFunction(this[ehandler])) { 226 | this.$el.on(ename, _.bind(this[ehandler], this)); 227 | } 228 | }, this); 229 | }, this); 230 | }, 231 | 232 | // Initial building 233 | buildLayout: function() { 234 | // Add base class for styling 235 | this.$el.addClass('vertical-timeline-container'); 236 | 237 | // Add template layout 238 | this.$el.html(this.templates.buttons({ 239 | data: this.options 240 | }) + this.templates.loading({})); 241 | 242 | // Get data 243 | this.$el.trigger('vt.layoutBuilt'); 244 | }, 245 | 246 | // Get data. Data can be from from Google Spreadsheet or JSON 247 | getData: function() { 248 | var thisVT = this; 249 | 250 | // Check if data is set and has data 251 | if (_.isArray(this.options.data) && this.options.data.length > 0) { 252 | this.data = this.options.data; 253 | this.$el.trigger('vt.gotData'); 254 | } 255 | else { 256 | Tabletop.init(_.extend({}, this.options.tabletopOptions, { 257 | key: this.options.key, 258 | wanted: [this.options.sheetName], 259 | callback: function(data, tabletop) { 260 | thisVT.data = data[thisVT.options.sheetName].elements; 261 | thisVT.tabletop = tabletop; 262 | thisVT.$el.trigger('vt.gotData'); 263 | } 264 | })); 265 | } 266 | }, 267 | 268 | // Process data 269 | parseData: function() { 270 | // Placeholder for groups 271 | this.groups = this.groups || {}; 272 | 273 | // Go through each row 274 | this.data = _.map(this.data, function(row) { 275 | // Column mapping. 276 | _.each(this.options.columnMapping, function(column, key) { 277 | if (!_.isUndefined(row[column])) { 278 | row[key] = row[column]; 279 | } 280 | }); 281 | 282 | // Parse date with moment 283 | row.date = moment(row.date, this.options.dateParse); 284 | row.timestamp = row.date.unix(); 285 | 286 | // Process into group 287 | this.groups = this.options.groupFunction(row, this.groups, this.options.defaultDirection); 288 | 289 | return row; 290 | }, this); 291 | 292 | // Trigger done 293 | this.$el.trigger('vt.parsedData'); 294 | }, 295 | 296 | // Build timline 297 | buildTimeline: function() { 298 | this.$el.append(this.templates.timeline({ 299 | data: { 300 | posts: _.map(this.data, function(d, di) { 301 | return this.templates.post({ 302 | data: d, 303 | options: this.options 304 | }); 305 | }, this).join(' '), 306 | groups: _.map(this.groups, function(g, gi) { 307 | return this.templates.group({ 308 | data: g 309 | }); 310 | }, this).join(' ') 311 | } 312 | })); 313 | 314 | this.$timeline = this.$el.find('.vertical-timeline-timeline'); 315 | this.$el.trigger('vt.builtTimeline'); 316 | }, 317 | 318 | // Wait for images to be loaded 319 | loadImages: function() { 320 | this.$el.imagesLoaded(_.bind(function() { 321 | this.$el.find('.loading').slideUp(); 322 | this.$timeline.fadeIn('fast', _.bind(function() { 323 | this.$el.trigger('vt.loadedImages'); 324 | }, this)); 325 | }, this)); 326 | }, 327 | 328 | // Make isotope layout 329 | isotopeIt: function() { 330 | this.$el.find('.vertical-timeline-timeline').isotope({ 331 | itemSelector: '.item', 332 | transformsEnabled: true, 333 | layoutMode: 'spineAlign', 334 | spineAlign:{ 335 | gutterWidth: this.options.gutterWidth 336 | }, 337 | getSortData: { 338 | timestamp: function($el) { 339 | return parseFloat($el.data('timestamp')); 340 | } 341 | }, 342 | sortBy: 'timestamp', 343 | sortAscending: (this.options.defaultDirection === 'newest') ? false : true, 344 | itemPositionDataEnabled: true, 345 | onLayout: _.bind(function($els, instance) { 346 | this.$el.trigger('vt.isotopized'); 347 | }, this), 348 | containerStyle: { 349 | position: 'relative' 350 | } 351 | }); 352 | }, 353 | 354 | // Adjust width of timeline 355 | adjustWidth: function() { 356 | var w = this.options.width; 357 | var containerW = this.$el.width(); 358 | var timelineW; 359 | var postW; 360 | 361 | if (w === 'auto') { 362 | w = containerW + 'px'; 363 | } 364 | 365 | // Set timeline width 366 | this.$timeline.css('width', w); 367 | timelineW = this.$timeline.width(); 368 | 369 | // Set width on posts 370 | postW = (timelineW / 2) - (this.options.gutterWidth / 2) - 3; 371 | this.$timeline.find('.post').width(postW); 372 | }, 373 | 374 | // Adjust the middle line 375 | adjustSpine: function() { 376 | var $lastItem = this.$el.find('.item.last'); 377 | var itemPosition = $lastItem.data('isotope-item-position'); 378 | var dateHeight = $lastItem.find('.date').height(); 379 | var dateOffset = $lastItem.find('.date').position(); 380 | var innerMargin = parseInt($lastItem.find('.inner').css('marginTop'), 10); 381 | var top = (dateOffset === undefined) ? 0 : parseInt(dateOffset.top, 10); 382 | var y = (itemPosition && itemPosition.y) ? 383 | parseInt(itemPosition.y, 10) : 0; 384 | var lineHeight = y + innerMargin + top + (dateHeight / 2); 385 | var $line = this.$el.find('.line'); 386 | var xOffset = (this.$timeline.width() / 2) - ($line.width() / 2); 387 | 388 | $line.height(lineHeight) 389 | .css('left', xOffset + 'px'); 390 | }, 391 | 392 | // DOM event 393 | domEvents: function() { 394 | if (this.domEventsAdded) { 395 | this.$el.trigger('vt.domEventsAdded'); 396 | return; 397 | } 398 | 399 | // Handle click of open close buttons on post 400 | this.$el.find('.item a.open-close').on('click', _.bind(function(e) { 401 | e.preventDefault(); 402 | var $thisButton = $(e.currentTarget); 403 | var $post = $thisButton.parents('.post'); 404 | var direction = ($post.hasClass('collapsed')) ? 'slideDown' : 'slideUp'; 405 | 406 | // Slide body 407 | $thisButton.siblings('.body')[direction](_.bind(function() { 408 | // Mark post and poke isotope 409 | $post.toggleClass('collapsed').toggleClass('expanded'); 410 | this.$timeline.isotope('reLayout'); 411 | }, this)); 412 | // Change top buttons 413 | this.$el.find('.expand-collapse-buttons a').removeClass('active'); 414 | }, this)); 415 | 416 | // Handle expand/collapse buttons 417 | this.$el.find('.vertical-timeline-buttons a.expand-all').on('click', _.bind(function(e) { 418 | e.preventDefault(); 419 | var $this = $(e.currentTarget); 420 | var thisVT = this; 421 | 422 | this.$el.find('.post .body').slideDown(function() { 423 | thisVT.$timeline.isotope('reLayout'); 424 | }); 425 | this.$el.find('.post').removeClass('collapsed').addClass('expanded'); 426 | this.$el.find('.expand-collapse-buttons a').removeClass('active'); 427 | $this.addClass('active'); 428 | }, this)); 429 | this.$el.find('.vertical-timeline-buttons a.collapse-all').on('click', _.bind(function(e) { 430 | e.preventDefault(); 431 | var $this = $(e.currentTarget); 432 | var thisVT = this; 433 | 434 | this.$el.find('.post .body').slideUp(function() { 435 | thisVT.$timeline.isotope('reLayout'); 436 | }); 437 | this.$el.find('.post').addClass('collapsed').removeClass('expanded'); 438 | this.$el.find('.expand-collapse-buttons a').removeClass('active'); 439 | $this.addClass('active'); 440 | }, this)); 441 | 442 | // Sorting buttons 443 | this.$el.find('.sort-buttons a').on('click', _.bind(function(e) { 444 | e.preventDefault(); 445 | var $this = $(e.currentTarget); 446 | 447 | // Don't proceed if already selected 448 | if ($this.hasClass('active')) { 449 | return false; 450 | } 451 | 452 | // Mark buttons 453 | this.$el.find('.sort-buttons a').removeClass('active'); 454 | $this.addClass('active'); 455 | 456 | // Change sorting 457 | if ($this.hasClass('sort-newest')) { 458 | this.updateGroups('newest'); 459 | } 460 | else { 461 | this.updateGroups('oldest'); 462 | } 463 | }, this)); 464 | 465 | // If jQuery resize plugin is enabled and the option is 466 | // enabled then handle resize 467 | if (this.options.handleResize === true && _.isFunction(this.$el.resize)) { 468 | this.$el.resize(_.throttle(_.bind(function() { 469 | this.$el.trigger('vt.isotopized'); 470 | }, this), 200)); 471 | } 472 | 473 | // Only need to add these once 474 | this.domEventsAdded = true; 475 | this.$el.trigger('vt.domEventsAdded'); 476 | }, 477 | 478 | // Updates group markers with the appropriate timestamp 479 | // for isotope layout 480 | updateGroups: function(direction) { 481 | var thisVT = this; 482 | direction = direction || this.options.defaultDirection; 483 | 484 | this.$el.find('.group-marker').each(function() { 485 | var $this = $(this); 486 | var timestamp = (direction !== 'newest') ? 487 | thisVT.groups[$this.data('id')].timestampStart : 488 | thisVT.groups[$this.data('id')].timestampEnd; 489 | $this.data('timestamp', timestamp); 490 | }); 491 | 492 | // Poke isotope 493 | this.$timeline.isotope('reloadItems') 494 | .isotope({ sortAscending: (direction !== 'newest') }); 495 | } 496 | }); 497 | 498 | 499 | /** 500 | * Extend Isotope for custom layout: spineAlign 501 | */ 502 | _.extend($.Isotope.prototype, { 503 | _spineAlignReset: function() { 504 | this.spineAlign = { 505 | colA: 0, 506 | colB: 0, 507 | lastY: -60 508 | }; 509 | }, 510 | 511 | _spineAlignLayout: function( $elems ) { 512 | var instance = this, 513 | props = this.spineAlign, 514 | gutterWidth = Math.round( this.options.spineAlign && this.options.spineAlign.gutterWidth ) || 0, 515 | centerX = Math.round(this.element.width() / 2); 516 | 517 | $elems.each(function(i, val) { 518 | var $this = $(this); 519 | var x, y; 520 | 521 | $this.removeClass('last').removeClass('top'); 522 | if (i == $elems.length - 1) { 523 | $this.addClass('last'); 524 | } 525 | if ($this.hasClass('group-marker')) { 526 | var width = $this.width(); 527 | x = centerX - (width / 2); 528 | if (props.colA >= props.colB) { 529 | y = props.colA; 530 | if (y === 0) { 531 | $this.addClass('top'); 532 | } 533 | props.colA += $this.outerHeight(true); 534 | props.colB = props.colA; 535 | } 536 | else { 537 | y = props.colB; 538 | if (y === 0) { 539 | $this.addClass('top'); 540 | } 541 | props.colB += $this.outerHeight(true); 542 | props.colA = props.colB; 543 | } 544 | } 545 | else { 546 | $this.removeClass('left').removeClass('right'); 547 | var isColA = props.colB >= props.colA; 548 | if (isColA) { 549 | $this.addClass('left'); 550 | } 551 | else { 552 | $this.addClass('right'); 553 | } 554 | 555 | x = isColA ? 556 | centerX - ( $this.outerWidth(true) + gutterWidth / 2 ) : // left side 557 | centerX + (gutterWidth / 2); // right side 558 | y = isColA ? props.colA : props.colB; 559 | if (y - props.lastY <= 60) { 560 | var extraSpacing = 60 - Math.abs(y - props.lastY); 561 | $this.find('.inner').css('marginTop', extraSpacing); 562 | props.lastY = y + extraSpacing; 563 | } 564 | else { 565 | $this.find('.inner').css('marginTop', 0); 566 | props.lastY = y; 567 | } 568 | props[( isColA ? 'colA' : 'colB' )] += $this.outerHeight(true); 569 | } 570 | instance._pushPosition( $this, x, y ); 571 | }); 572 | }, 573 | 574 | _spineAlignGetContainerSize: function() { 575 | var size = {}; 576 | size.height = this.spineAlign[( this.spineAlign.colB > this.spineAlign.colA ? 'colB' : 'colA' )]; 577 | return size; 578 | }, 579 | 580 | _spineAlignResizeChanged: function() { 581 | return true; 582 | } 583 | }); 584 | 585 | /** 586 | * Turn verticalTimeline into jQuery plugin 587 | */ 588 | $.fn.verticalTimeline = function(options) { 589 | return this.each(function() { 590 | if (!$.data(this, 'verticalTimeline')) { 591 | $.data(this, 'verticalTimeline', new VerticalTimeline(this, options)); 592 | } 593 | }); 594 | }; 595 | 596 | // Incase someone wants to use the base class, return it 597 | return VerticalTimeline; 598 | 599 | }); 600 | -------------------------------------------------------------------------------- /dist/jquery-vertical-timeline.min.css: -------------------------------------------------------------------------------- 1 | /*! jquery-vertical-timeline - v0.3.0 - 2016-08-24 2 | * https://github.com/MinnPost/jquery-vertical-timeline 3 | * Copyright (c) 2016 MinnPost; Licensed MIT */ 4 | 5 | 6 | .vertical-timeline-container{*zoom:1}.vertical-timeline-container:after,.vertical-timeline-container:before{content:" ";display:table}.vertical-timeline-container:after{clear:both}.vertical-timeline-container .vertical-timeline-timeline{width:600px;margin:20px auto 30px;display:none}.vertical-timeline-container .clearfix{*zoom:1}.vertical-timeline-container .clearfix:after,.vertical-timeline-container .clearfix:before{content:" ";display:table}.vertical-timeline-container .clearfix:after{clear:both}.vertical-timeline-container .loading{text-align:center;font-size:2em;margin:1em;color:#565656}.vertical-timeline-container .post{width:271px;margin:0 0 10px;float:left}.vertical-timeline-container .post.last{margin-bottom:0}.vertical-timeline-container .post .inner{position:relative;padding:11px;border:1px #adadad solid;background-color:#fff;min-height:37px;word-wrap:break-word;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 1px 1px 0 #ADADAD;-webkit-box-shadow:0 1px 1px 0 #ADADAD;box-shadow:0 1px 1px 0 #ADADAD}.vertical-timeline-container .post.right{float:right}.vertical-timeline-container .post .timestamp{display:none}.vertical-timeline-container .post h3{margin:0;font-family:Georgia,sans-serif;font-size:20px;font-weight:400;color:#010101;padding-right:30px;line-height:1.3em}.vertical-timeline-container .post .caption{color:#9d9d9d;font-family:Georgia,sans-serif;font-size:12px;margin-top:4px}.vertical-timeline-container .post .body{margin-top:10px}.vertical-timeline-container .post.collapsed .body{display:none}.vertical-timeline-container .post.expanded .body{display:block}.vertical-timeline-container .post .body img{max-width:100%}.vertical-timeline-container .post .text{color:#393939;font-family:Georgia,sans-serif;font-size:15px;margin:10px 0;line-height:1.5}.vertical-timeline-container .post a.open-close{background:transparent url(./images/button-up-down-arrow.png?1471988818) no-repeat;height:17px;width:16px;position:absolute;right:11px;top:11px}.vertical-timeline-container .post.collapsed a.open-close{background-position:left bottom}.vertical-timeline-container .post .title .title-icon{height:20px;width:20px;margin-right:4px;vertical-align:top}.vertical-timeline-container .post.closed .title{display:table;min-height:40px}.vertical-timeline-container .post.closed .title h3{display:table-cell;vertical-align:middle}.vertical-timeline-container .post a.more{color:#676767;text-decoration:none;text-transform:uppercase;font-size:12px;font-weight:700;text-align:center;padding:4px 15px;margin:5px auto 0;border:1px #BDBDBD solid;display:block;line-height:28px;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 1px 1px 0 #BDBDBD;-webkit-box-shadow:0 1px 1px 0 #BDBDBD;box-shadow:0 1px 1px 0 #BDBDBD}.vertical-timeline-container .post a.more:hover{background-color:#FAFAFA}.vertical-timeline-container .post .date{color:#6a6a6a;font-size:11px;text-align:center;position:absolute;top:19px;display:block;width:54px;height:21px;line-height:20px}.vertical-timeline-container .post.left .date{background:transparent url(./images/tab-left.png?1471988818) no-repeat right;right:-54px}.vertical-timeline-container .post.right .date{background:transparent url(./images/tab-right.png?1471988818) no-repeat left;left:-54px}.vertical-timeline-container .group-marker{float:left;width:80px;margin:50px 0 15px}.vertical-timeline-container .group-marker .timestamp{display:none}.vertical-timeline-container .group-marker.top{margin-top:0}.vertical-timeline-container .group-marker .inner{width:76px;height:26px;padding:2px;background-color:#FFF}.vertical-timeline-container .group-marker .inner2{background-color:#434a50;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}.vertical-timeline-container .group{text-align:center;color:#fff;font-size:14px;font-weight:700;height:26px;line-height:26px}.vertical-timeline-container .vertical-timeline-buttons{text-align:center;margin:20px 0 10px}.vertical-timeline-container .vertical-timeline-buttons div{display:-moz-inline-stack;display:inline-block;zoom:1;*display:inline}.vertical-timeline-container .vertical-timeline-buttons .expand-collapse-buttons{margin-right:20px}.vertical-timeline-container .vertical-timeline-buttons a{display:-moz-inline-stack;display:inline-block;zoom:1;*display:inline;width:126px;border:1px #adadad solid;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;font-size:12px;font-weight:700;text-decoration:none;padding:10px 12px;background-color:#FFF;color:#3b3b3b;text-transform:uppercase}.vertical-timeline-container .vertical-timeline-buttons a.active,.vertical-timeline-container .vertical-timeline-buttons a.active:hover{background-color:#f6f6f6;color:#a9a9a9;cursor:default;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none}.vertical-timeline-container .vertical-timeline-buttons a:hover{-moz-box-shadow:0 1px 1px 0 #adadad;-webkit-box-shadow:0 1px 1px 0 #adadad;box-shadow:0 1px 1px 0 #adadad}.vertical-timeline-container .vertical-timeline-buttons a span{padding-right:15px;background-position:right -20px}.vertical-timeline-container .vertical-timeline-buttons a.active span{background-position:right 4px}.vertical-timeline-container .vertical-timeline-buttons a.expand-all span,.vertical-timeline-container .vertical-timeline-buttons a.sort-newest span{background-image:url(./images/down-arrows.png?1471988818);background-repeat:no-repeat}.vertical-timeline-container .vertical-timeline-buttons a.collapse-all span,.vertical-timeline-container .vertical-timeline-buttons a.sort-oldest span{background-image:url(./images/up-arrows.png?1471988818);background-repeat:no-repeat}@media (max-width:675px){.vertical-timeline-container .vertical-timeline-buttons .expand-collapse-buttons,.vertical-timeline-container .vertical-timeline-buttons .sort-buttons{margin:0 0 10px}}.vertical-timeline-container .line-container{width:4px;text-align:center;margin:0 auto;display:block}.vertical-timeline-container .isotope .line{margin:0 auto;background-color:#b3b6b8;display:block;float:left;height:100%;left:298px;width:4px;position:absolute}.vertical-timeline-container .isotope-item{z-index:2}.vertical-timeline-container .isotope-hidden.isotope-item{pointer-events:none;z-index:1}.vertical-timeline-container .vertical-timeline-container .isotope,.vertical-timeline-container .vertical-timeline-container .isotope .isotope-item{-moz-transition-duration:.8s;-o-transition-duration:.8s;-webkit-transition-duration:.8s;transition-duration:.8s}.vertical-timeline-container .vertical-timeline-container .isotope{-moz-transition-property:height,width;-o-transition-property:height,width;-webkit-transition-property:height,width;transition-property:height,width}.vertical-timeline-container .vertical-timeline-container .isotope .isotope-item{-webkit-transition-property:-webkit-transform,opacity;-moz-transition-property:-moz-transform,opacity;-ms-transition-property:-ms-transform,opacity;-o-transition-property:top,left,opacity;transition-property:transform,opacity}.vertical-timeline-container .isotope .isotope-item.no-transition,.vertical-timeline-container .isotope.no-transition,.vertical-timeline-container .isotope.no-transition .isotope-item{-moz-transition-duration:0s;-o-transition-duration:0s;-webkit-transition-duration:0s;transition-duration:0s} -------------------------------------------------------------------------------- /dist/jquery-vertical-timeline.min.js: -------------------------------------------------------------------------------- 1 | /*! jquery-vertical-timeline - v0.3.0 - 2016-08-24 2 | * https://github.com/MinnPost/jquery-vertical-timeline 3 | * Copyright (c) 2016 MinnPost; Licensed MIT */ 4 | 5 | !function(a,b){if("undefined"!=typeof module&&module.exports&&"function"==typeof require)b(require("jquery"),require("underscore"),require("tabletop"),require("isotope"),require("imagesloaded"),require("moment"));else if("function"==typeof define&&define.amd)define("jquery-vertical-timeline",["jquery","underscore","tabletop","moment","isotope","imagesloaded"],b);else{if(!(a.jQuery&&a._&&a.Tabletop&&a.moment&&a.jQuery.fn.isotope&&a.jQuery.fn.imagesLoaded))throw new Error("Could not find dependencies for jQuery Vertical Timeline.");b(a.jQuery,a._,a.Tabletop,a.moment,a.jQuery.fn.isotope,a.jQuery.fn.resize,a.jQuery.fn.imagesLoaded)}}("undefined"!=typeof window?window:this,function(a,b,c,d){var e={key:"https://docs.google.com/spreadsheet/pub?key=0AsmHVq28GtVJdG1fX3dsQlZrY18zTVA2ZG8wTXdtNHc&output=html",sheetName:"Posts",dateParse:["MMM DD, YYYY","MM/DD/YYYY","M/D/YYYY","DD MMM YYYY"],defaultDirection:"newest",defaultExpansion:"expanded",groupFunction:"groupSegmentByYear",sharing:!1,gutterWidth:57,width:"auto",handleResize:!1,tabletopOptions:{},columnMapping:{title:"title",title_icon:"title icon",date:"date",display_date:"display date",photo_url:"photo url",caption:"caption",body:"body",read_more_url:"read more url"},postTemplate:'
',groupMarkerTemplate:'
<%= data.groupDisplay %>
',buttonTemplate:' ',timelineTemplate:'
<%= data.posts %> <%= data.groups %>
',loadingTemplate:'
Loading...
'},f={};f.groupSegmentByDecade=function(a,c,e){var f=a.date.year(),g=f.toString(),h=g.slice(0,-1),i=d(h+"0-01-01T00:00:00"),j=d(h+"9-12-31T12:59:99");return b.isUndefined(c[h])&&(c[h]={id:h,groupDisplay:h+"0s",timestamp:"newest"==e?j.unix():i.unix(),timestampStart:i.unix(),timestampEnd:j.unix()}),c},f.groupSegmentByYear=function(a,c,e){var f=a.date.year(),g=d(f+"-01-01T00:00:00"),h=d(f+"-12-31T12:59:99");return b.isUndefined(c[f.toString()])&&(c[f.toString()]={id:f,groupDisplay:f,timestamp:"newest"==e?h.unix():g.unix(),timestampStart:g.unix(),timestampEnd:h.unix()}),c},f.groupSegmentByDay=function(a,b,c){var d=new Date(a.timestamp).getMonth(),e=new Date(a.timestamp).getFullYear(),f=new Date(a.timestamp).getDate(),g=["Jan","Feb","Mar","Apr","May","June","July","Aug","Sept","Oct","Nov","Dec"],h=Date.parse(g[d]+" "+f+", "+e),i=Date.parse(g[d]+" "+(f+1)+", "+e),j=f+100*(d+100*e);return b[j]={id:j,groupDisplay:g[d]+" "+f+", "+e,timestamp:"newest"==c?i:h,timestampStart:h,timestampEnd:i},b};var g=function(c,d){this.options=a.extend(!0,{},e,d),this.options.groupFunction=!b.isFunction(this.options.groupFunction)&&b.isFunction(f[this.options.groupFunction])?f[this.options.groupFunction]:this.options.groupFunction,this.$el=a(c),this.templates={},this.templates.post=b.template(this.options.postTemplate),this.templates.group=b.template(this.options.groupMarkerTemplate),this.templates.buttons=b.template(this.options.buttonTemplate),this.templates.timeline=b.template(this.options.timelineTemplate),this.templates.loading=b.template(this.options.loadingTemplate),this.loadEvents(),this.$el.trigger("vt.build")};return b.extend(g.prototype,{events:{"vt.build":["buildLayout"],"vt.layoutBuilt":["getData"],"vt.gotData":["parseData"],"vt.parsedData":["buildTimeline"],"vt.builtTimeline":["loadImages","adjustWidth"],"vt.loadedImages":["isotopeIt"],"vt.isotopized":["adjustWidth","adjustSpine","domEvents"]},loadEvents:function(){b.each(this.events,function(a,c){b.each(a,function(a){b.isFunction(this[a])&&this.$el.on(c,b.bind(this[a],this))},this)},this)},buildLayout:function(){this.$el.addClass("vertical-timeline-container"),this.$el.html(this.templates.buttons({data:this.options})+this.templates.loading({})),this.$el.trigger("vt.layoutBuilt")},getData:function(){var a=this;b.isArray(this.options.data)&&this.options.data.length>0?(this.data=this.options.data,this.$el.trigger("vt.gotData")):c.init(b.extend({},this.options.tabletopOptions,{key:this.options.key,wanted:[this.options.sheetName],callback:function(b,c){a.data=b[a.options.sheetName].elements,a.tabletop=c,a.$el.trigger("vt.gotData")}}))},parseData:function(){this.groups=this.groups||{},this.data=b.map(this.data,function(a){return b.each(this.options.columnMapping,function(c,d){b.isUndefined(a[c])||(a[d]=a[c])}),a.date=d(a.date,this.options.dateParse),a.timestamp=a.date.unix(),this.groups=this.options.groupFunction(a,this.groups,this.options.defaultDirection),a},this),this.$el.trigger("vt.parsedData")},buildTimeline:function(){this.$el.append(this.templates.timeline({data:{posts:b.map(this.data,function(a,b){return this.templates.post({data:a,options:this.options})},this).join(" "),groups:b.map(this.groups,function(a,b){return this.templates.group({data:a})},this).join(" ")}})),this.$timeline=this.$el.find(".vertical-timeline-timeline"),this.$el.trigger("vt.builtTimeline")},loadImages:function(){this.$el.imagesLoaded(b.bind(function(){this.$el.find(".loading").slideUp(),this.$timeline.fadeIn("fast",b.bind(function(){this.$el.trigger("vt.loadedImages")},this))},this))},isotopeIt:function(){this.$el.find(".vertical-timeline-timeline").isotope({itemSelector:".item",transformsEnabled:!0,layoutMode:"spineAlign",spineAlign:{gutterWidth:this.options.gutterWidth},getSortData:{timestamp:function(a){return parseFloat(a.data("timestamp"))}},sortBy:"timestamp",sortAscending:"newest"===this.options.defaultDirection?!1:!0,itemPositionDataEnabled:!0,onLayout:b.bind(function(a,b){this.$el.trigger("vt.isotopized")},this),containerStyle:{position:"relative"}})},adjustWidth:function(){var a,b,c=this.options.width,d=this.$el.width();"auto"===c&&(c=d+"px"),this.$timeline.css("width",c),a=this.$timeline.width(),b=a/2-this.options.gutterWidth/2-3,this.$timeline.find(".post").width(b)},adjustSpine:function(){var a=this.$el.find(".item.last"),b=a.data("isotope-item-position"),c=a.find(".date").height(),d=a.find(".date").position(),e=parseInt(a.find(".inner").css("marginTop"),10),f=void 0===d?0:parseInt(d.top,10),g=b&&b.y?parseInt(b.y,10):0,h=g+e+f+c/2,i=this.$el.find(".line"),j=this.$timeline.width()/2-i.width()/2;i.height(h).css("left",j+"px")},domEvents:function(){return this.domEventsAdded?void this.$el.trigger("vt.domEventsAdded"):(this.$el.find(".item a.open-close").on("click",b.bind(function(c){c.preventDefault();var d=a(c.currentTarget),e=d.parents(".post"),f=e.hasClass("collapsed")?"slideDown":"slideUp";d.siblings(".body")[f](b.bind(function(){e.toggleClass("collapsed").toggleClass("expanded"),this.$timeline.isotope("reLayout")},this)),this.$el.find(".expand-collapse-buttons a").removeClass("active")},this)),this.$el.find(".vertical-timeline-buttons a.expand-all").on("click",b.bind(function(b){b.preventDefault();var c=a(b.currentTarget),d=this;this.$el.find(".post .body").slideDown(function(){d.$timeline.isotope("reLayout")}),this.$el.find(".post").removeClass("collapsed").addClass("expanded"),this.$el.find(".expand-collapse-buttons a").removeClass("active"),c.addClass("active")},this)),this.$el.find(".vertical-timeline-buttons a.collapse-all").on("click",b.bind(function(b){b.preventDefault();var c=a(b.currentTarget),d=this;this.$el.find(".post .body").slideUp(function(){d.$timeline.isotope("reLayout")}),this.$el.find(".post").addClass("collapsed").removeClass("expanded"),this.$el.find(".expand-collapse-buttons a").removeClass("active"),c.addClass("active")},this)),this.$el.find(".sort-buttons a").on("click",b.bind(function(b){b.preventDefault();var c=a(b.currentTarget);return c.hasClass("active")?!1:(this.$el.find(".sort-buttons a").removeClass("active"),c.addClass("active"),void(c.hasClass("sort-newest")?this.updateGroups("newest"):this.updateGroups("oldest")))},this)),this.options.handleResize===!0&&b.isFunction(this.$el.resize)&&this.$el.resize(b.throttle(b.bind(function(){this.$el.trigger("vt.isotopized")},this),200)),this.domEventsAdded=!0,void this.$el.trigger("vt.domEventsAdded"))},updateGroups:function(b){var c=this;b=b||this.options.defaultDirection,this.$el.find(".group-marker").each(function(){var d=a(this),e="newest"!==b?c.groups[d.data("id")].timestampStart:c.groups[d.data("id")].timestampEnd;d.data("timestamp",e)}),this.$timeline.isotope("reloadItems").isotope({sortAscending:"newest"!==b})}}),b.extend(a.Isotope.prototype,{_spineAlignReset:function(){this.spineAlign={colA:0,colB:0,lastY:-60}},_spineAlignLayout:function(b){var c=this,d=this.spineAlign,e=Math.round(this.options.spineAlign&&this.options.spineAlign.gutterWidth)||0,f=Math.round(this.element.width()/2);b.each(function(g,h){var i,j,k=a(this);if(k.removeClass("last").removeClass("top"),g==b.length-1&&k.addClass("last"),k.hasClass("group-marker")){var l=k.width();i=f-l/2,d.colA>=d.colB?(j=d.colA,0===j&&k.addClass("top"),d.colA+=k.outerHeight(!0),d.colB=d.colA):(j=d.colB,0===j&&k.addClass("top"),d.colB+=k.outerHeight(!0),d.colA=d.colB)}else{k.removeClass("left").removeClass("right");var m=d.colB>=d.colA;if(m?k.addClass("left"):k.addClass("right"),i=m?f-(k.outerWidth(!0)+e/2):f+e/2,j=m?d.colA:d.colB,j-d.lastY<=60){var n=60-Math.abs(j-d.lastY);k.find(".inner").css("marginTop",n),d.lastY=j+n}else k.find(".inner").css("marginTop",0),d.lastY=j;d[m?"colA":"colB"]+=k.outerHeight(!0)}c._pushPosition(k,i,j)})},_spineAlignGetContainerSize:function(){var a={};return a.height=this.spineAlign[this.spineAlign.colB>this.spineAlign.colA?"colB":"colA"],a},_spineAlignResizeChanged:function(){return!0}}),a.fn.verticalTimeline=function(b){return this.each(function(){a.data(this,"verticalTimeline")||a.data(this,"verticalTimeline",new g(this,b))})},g}); -------------------------------------------------------------------------------- /example/example.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JS for example using RequireJS 3 | */ 4 | 5 | require.config({ 6 | shim: { 7 | 'underscore': { 8 | exports: '_' 9 | }, 10 | 'handlebars': { 11 | exports: 'Handlebars' 12 | }, 13 | 'tabletop': { 14 | exports: 'Tabletop' 15 | }, 16 | 'isotope': { 17 | deps: ['jquery'] 18 | }, 19 | 'jquery-resize': { 20 | deps: ['jquery'] 21 | } 22 | }, 23 | paths: { 24 | 'jquery': '../bower_components/jquery/jquery.min', 25 | 'underscore': '../bower_components/underscore/underscore-min', 26 | 'tabletop': '../bower_components/tabletop/src/tabletop', 27 | 'moment': '../bower_components/momentjs/min/moment.min', 28 | 'isotope': '../bower_components/isotope/jquery.isotope.min', 29 | 'jquery-resize': '../bower_components/jquery-resize/jquery.ba-resize.min', 30 | 'eventEmitter/EventEmitter': '../bower_components/eventEmitter/EventEmitter.min', 31 | 'eventie/eventie': '../bower_components/eventie/eventie', 32 | 'imagesloaded': '../bower_components/imagesloaded/imagesloaded', 33 | 'jquery-vertical-timeline': '../dist/jquery-vertical-timeline.min' 34 | } 35 | }); 36 | 37 | require(['jquery', 'underscore', 'jquery-resize', 'jquery-vertical-timeline'], function($, _) { 38 | $(document).ready(function() { 39 | $('.timeline-jquery-example-mn-gambling').verticalTimeline({ 40 | key: 'https://docs.google.com/spreadsheet/pub?key=0AtX8MXQ89fOKdDZadGcyNE9CZmFZV29tQjI5RFU3X3c&output=html', 41 | sheetName: 'timeline-data', 42 | dateParse: 'MMM DD, YYYY', 43 | defaultDirection: 'oldest', 44 | defaultExpansion: 'collapsed', 45 | groupFunction: 'groupSegmentByDecade', 46 | width: '50%', 47 | tabletopOptions: { 48 | parameterize: 'http://gs-proxy.herokuapp.com/proxy?url=' 49 | } 50 | }); 51 | 52 | $.getJSON('./example/example.json', function(data) { 53 | $('.timeline-jquery-example-kitten').verticalTimeline({ 54 | data: data, 55 | dateParse: 'DD MMM YYYY', 56 | width: '75%' 57 | }); 58 | }); 59 | 60 | $('.timeline-jquery-example-mn-example').verticalTimeline({ 61 | key: '0AjYft7IGrHzNdHVTTi1xRGtpdHV5SzJDZW4yRnlRN2c', 62 | sheetName: 'Entries', 63 | dateParse: 'M/D/YYYY', 64 | tabletopOptions: { 65 | parameterize: 'http://gs-proxy.herokuapp.com/proxy?url=' 66 | } 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /example/example.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "This timeline uses JSON data directly", 4 | "date": "27 Mar 2008", 5 | "display date": "Mar 27", 6 | "photourl": "http://placekitten.com/600/400", 7 | "caption": "Mike Baird", 8 | "body": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec qu", 9 | "read more url": "http://www.flickr.com/photos/mikebaird/2529507825/" 10 | }, 11 | { 12 | "title": "Peregrine Falcon Adult, Morro Bay, CA", 13 | "date": "27 May 2008", 14 | "display date": "May 27", 15 | "photo url": "http://placekitten.com/400/500", 16 | "caption": "Mike Baird", 17 | "body": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec qu", 18 | "read more url": "http://www.flickr.com/photos/mikebaird/2529507825/" 19 | }, 20 | { 21 | "title": "Peregrine Falcon Adult, Morro Bay, CA", 22 | "date": "27 Oct 2008", 23 | "display date": "Oct 27", 24 | "photo url": "http://placekitten.com/500/220", 25 | "caption": "Mike Baird", 26 | "body": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec qu", 27 | "read more url": "http://www.flickr.com/photos/mikebaird/2529507825/" 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /example/style.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Example CSS 3 | */ 4 | 5 | body { 6 | margin: 0; 7 | padding: 0 1em; 8 | } 9 | 10 | .example { 11 | padding-bottom: 4em; 12 | margin-bottom: 4em; 13 | border-bottom: 1px solid #BABABA; 14 | } 15 | 16 | .example h1 { 17 | max-width: 960px; 18 | margin: 0 auto; 19 | text-align: center; 20 | text-transform: uppercase; 21 | margin-top: 1em; 22 | } 23 | 24 | .example a.spreadsheet-link, 25 | .example a.spreadsheet-link:visited { 26 | color: blue; 27 | display: block; 28 | max-width: 960px; 29 | margin: 0 auto; 30 | text-align: center; 31 | font-size: 1.25em; 32 | } 33 | 34 | code { 35 | max-width: 960px; 36 | margin: 0 auto; 37 | display: block; 38 | padding: .85em; 39 | margin-bottom: 2em; 40 | background-color: #EFEFEF; 41 | border-radius: .25em; 42 | } 43 | 44 | /** 45 | * http://nicolasgallagher.com/micro-clearfix-hack/ 46 | * For modern browsers 47 | * 1. The space content is one way to avoid an Opera bug when the 48 | * contenteditable attribute is included anywhere else in the document. 49 | * Otherwise it causes space to appear at the top and bottom of elements 50 | * that are clearfixed. 51 | * 2. The use of `table` rather than `block` is only necessary if using 52 | * `:before` to contain the top-margins of child elements. 53 | */ 54 | .cf:before, 55 | .cf:after { 56 | content: " "; /* 1 */ 57 | display: table; /* 2 */ 58 | } 59 | 60 | .cf:after { 61 | clear: both; 62 | } 63 | 64 | /** 65 | * For IE 6/7 only 66 | * Include this rule to trigger hasLayout and contain floats. 67 | */ 68 | .cf { 69 | *zoom: 1; 70 | } -------------------------------------------------------------------------------- /images/button-up-down-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinnPost/jquery-vertical-timeline/609c704f4077d097fcc0e400135c644cf049b5c4/images/button-up-down-arrow.png -------------------------------------------------------------------------------- /images/down-arrows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinnPost/jquery-vertical-timeline/609c704f4077d097fcc0e400135c644cf049b5c4/images/down-arrows.png -------------------------------------------------------------------------------- /images/icon-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinnPost/jquery-vertical-timeline/609c704f4077d097fcc0e400135c644cf049b5c4/images/icon-link.png -------------------------------------------------------------------------------- /images/icon-share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinnPost/jquery-vertical-timeline/609c704f4077d097fcc0e400135c644cf049b5c4/images/icon-share.png -------------------------------------------------------------------------------- /images/popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinnPost/jquery-vertical-timeline/609c704f4077d097fcc0e400135c644cf049b5c4/images/popup.png -------------------------------------------------------------------------------- /images/tab-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinnPost/jquery-vertical-timeline/609c704f4077d097fcc0e400135c644cf049b5c4/images/tab-left.png -------------------------------------------------------------------------------- /images/tab-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinnPost/jquery-vertical-timeline/609c704f4077d097fcc0e400135c644cf049b5c4/images/tab-right.png -------------------------------------------------------------------------------- /images/up-arrows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinnPost/jquery-vertical-timeline/609c704f4077d097fcc0e400135c644cf049b5c4/images/up-arrows.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | jQuery Vertical Example 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |

Kitten JSON Example

19 | JSON 20 |
// Uses example/example.json data soure
21 | {
22 |   data: data,
23 |   width: '75%'
24 | }
25 |
26 |
27 | 28 | 29 | 30 |
31 |

Minnesota Gambling Example

32 | Spreadsheet 33 |
{
34 |   key: 'https://docs.google.com/spreadsheet/pub?key=0AtX8MXQ89fOKdDZadGcyNE9CZmFZV29tQjI5RFU3X3c&output=html',
35 |   sheetName: 'timeline-data',
36 |   defaultDirection: 'oldest',
37 |   defaultExpansion: 'collapsed',
38 |   groupFunction: 'groupSegmentByDecade',
39 |   width: '50%',
40 |   tabletopOptions: {
41 |     parameterize: 'http://gs-proxy.herokuapp.com/proxy?url='
42 |   }
43 | }
44 |
45 |
46 | 47 | 48 | 49 |
50 |

Minnesota Marriage Example

51 | Spreadsheet 52 |
{
53 |   key: '0AjYft7IGrHzNdHVTTi1xRGtpdHV5SzJDZW4yRnlRN2c',
54 |   sheetName: 'Entries',
55 |   tabletopOptions: {
56 |     parameterize: 'http://gs-proxy.herokuapp.com/proxy?url='
57 |   }
58 | }
59 |
60 |
61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /js/jquery-vertical-timeline.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Vertical timeline plugin for jQuery. 3 | */ 4 | 5 | // Wrapper to handle using with RequireJS or Browserify or as a global 6 | (function(global, factory) { 7 | // Common JS (i.e. browserify) environment 8 | if (typeof module !== 'undefined' && module.exports && typeof require === 'function') { 9 | factory(require('jquery'), require('underscore'), require('tabletop'), require('isotope'), require('imagesloaded'), require('moment')); 10 | } 11 | // AMD? 12 | else if (typeof define === 'function' && define.amd) { 13 | define('jquery-vertical-timeline', ['jquery', 'underscore', 'tabletop', 'moment', 'isotope', 'imagesloaded'], factory); 14 | } 15 | // Browser global 16 | else if (global.jQuery && global._ && global.Tabletop && global.moment && global.jQuery.fn.isotope && global.jQuery.fn.imagesLoaded) { 17 | factory(global.jQuery, global._, global.Tabletop, global.moment, global.jQuery.fn.isotope, global.jQuery.fn.resize, global.jQuery.fn.imagesLoaded); 18 | } 19 | else { 20 | throw new Error('Could not find dependencies for jQuery Vertical Timeline.' ); 21 | } 22 | })(typeof window !== 'undefined' ? window : this, function($, _, Tabletop, moment) { 23 | 24 | /** 25 | * Default options 26 | */ 27 | var defaultsOptions = { 28 | key: 'https://docs.google.com/spreadsheet/pub?key=0AsmHVq28GtVJdG1fX3dsQlZrY18zTVA2ZG8wTXdtNHc&output=html', 29 | sheetName: 'Posts', 30 | dateParse: ['MMM DD, YYYY', 'MM/DD/YYYY', 'M/D/YYYY', 'DD MMM YYYY'], 31 | defaultDirection: 'newest', 32 | defaultExpansion: 'expanded', 33 | groupFunction: 'groupSegmentByYear', 34 | sharing: false, 35 | gutterWidth: 57, 36 | width: 'auto', 37 | handleResize: false, 38 | tabletopOptions: {}, 39 | columnMapping: { 40 | 'title': 'title', 41 | 'title_icon': 'title icon', 42 | 'date': 'date', 43 | 'display_date': 'display date', 44 | 'photo_url': 'photo url', 45 | 'caption': 'caption', 46 | 'body': 'body', 47 | 'read_more_url': 'read more url' 48 | }, 49 | postTemplate: '
\ 50 |
\ 51 |
\ 52 |

\ 53 | <% if (data.title_icon) { %><% } %> \ 54 | <%= data.title %> \ 55 | <\/h3> \ 56 | <\/div> \ 57 |
<%= data.display_date %><\/div> \ 58 |
\ 59 | <% if (data.photo_url) { %> \ 60 | \ 61 | <% } %> \ 62 | <% if (data.caption) { %> \ 63 |
<%= data.caption %><\/div> \ 64 | <% } %> \ 65 | <% if (data.body) { %> \ 66 |
<%= data.body %><\/div> \ 67 | <% } %> \ 68 |
\ 69 | <% if (data.read_more_url) { %> \ 70 | Read more<\/a> \ 71 | <% } %> \ 72 | <\/div> \ 73 | <\/div> \ 74 | \ 75 | <\/div> \ 76 | <\/div> \ 77 | ', 78 | groupMarkerTemplate: '
\ 79 |
\ 80 |
\ 81 |
<%= data.groupDisplay %><\/div> \ 82 | <\/div> \ 83 | <\/div> \ 84 | <\/div> \ 85 | ', 86 | buttonTemplate: '
\ 87 |
\ 88 | active<% } %>" href="#">Expand all<\/span><\/a> \ 89 | active<% } %>" href="#">Collapse all<\/span><\/a> \ 90 | <\/div> \ 91 |
\ 92 | active<% } %>" href="#">Newest first<\/span><\/a> \ 93 | active<% } %>" href="#">Oldest first<\/span><\/a> \ 94 | <\/div> \ 95 | <\/div> \ 96 | ', 97 | timelineTemplate: '
\ 98 |
\ 99 |
<\/div> \ 100 | <\/div> \ 101 | <%= data.posts %> \ 102 | <%= data.groups %> \ 103 | <\/div> \ 104 | ', 105 | loadingTemplate: '
\ 106 | Loading... \ 107 | <\/div> \ 108 | ' 109 | }; 110 | 111 | var groupingFunctions = {}; 112 | /** 113 | * Grouping function by Decade. 114 | */ 115 | groupingFunctions.groupSegmentByDecade = function(row, groups, direction) { 116 | var year = row.date.year(); 117 | var yearStr = year.toString(); 118 | var id = yearStr.slice(0, -1); 119 | var start = moment(id + '0-01-01T00:00:00'); 120 | var end = moment(id + '9-12-31T12:59:99'); 121 | 122 | if (_.isUndefined(groups[id])) { 123 | groups[id] = { 124 | id: id, 125 | groupDisplay: id + '0s', 126 | timestamp: (direction == 'newest') ? end.unix() : start.unix(), 127 | timestampStart: start.unix(), 128 | timestampEnd: end.unix() 129 | }; 130 | } 131 | 132 | return groups; 133 | }; 134 | 135 | /** 136 | * Grouping function by year. 137 | */ 138 | groupingFunctions.groupSegmentByYear = function(row, groups, direction) { 139 | var year = row.date.year(); 140 | var start = moment(year + '-01-01T00:00:00'); 141 | var end = moment(year + '-12-31T12:59:99'); 142 | 143 | if (_.isUndefined(groups[year.toString()])) { 144 | groups[year.toString()] = { 145 | id: year, 146 | groupDisplay: year, 147 | timestamp: (direction == 'newest') ? end.unix() : start.unix(), 148 | timestampStart: start.unix(), 149 | timestampEnd: end.unix() 150 | }; 151 | } 152 | 153 | return groups; 154 | }; 155 | 156 | /** 157 | * Grouping function by day. 158 | */ 159 | groupingFunctions.groupSegmentByDay = function(row, groups, direction) { 160 | var month = new Date(row.timestamp).getMonth(); 161 | var year = new Date(row.timestamp).getFullYear(); 162 | var day = new Date(row.timestamp).getDate(); 163 | var _month_str = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'July', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec']; 164 | var _time_start = Date.parse(_month_str[month] + ' ' + day + ', ' + year); 165 | var _time_end = Date.parse(_month_str[month] + ' ' + (day+1) + ', ' + year); 166 | var _id = day + (month + year * 100) * 100; 167 | 168 | groups[_id] = { 169 | id: _id, 170 | groupDisplay: _month_str[month] + ' ' + day + ', ' + year, 171 | timestamp: (direction == 'newest') ? _time_end: _time_start, 172 | timestampStart: _time_start, 173 | timestampEnd: _time_end 174 | }; 175 | 176 | return groups; 177 | }; 178 | 179 | 180 | 181 | /** 182 | * Base class for timeline 183 | */ 184 | var VerticalTimeline = function(el, options) { 185 | this.options = $.extend(true, {}, defaultsOptions, options); 186 | 187 | // Check to see if grouping function is an option 188 | this.options.groupFunction = (!_.isFunction(this.options.groupFunction) && _.isFunction(groupingFunctions[this.options.groupFunction])) ? groupingFunctions[this.options.groupFunction] : this.options.groupFunction; 189 | 190 | // Consistent reference to jquery object 191 | this.$el = $(el); 192 | 193 | // Build templates for performance 194 | this.templates = {}; 195 | this.templates.post = _.template(this.options.postTemplate); 196 | this.templates.group = _.template(this.options.groupMarkerTemplate); 197 | this.templates.buttons = _.template(this.options.buttonTemplate); 198 | this.templates.timeline = _.template(this.options.timelineTemplate); 199 | this.templates.loading = _.template(this.options.loadingTemplate); 200 | 201 | // Use custom events to make things happens 202 | this.loadEvents(); 203 | this.$el.trigger('vt.build'); 204 | }; 205 | 206 | /** 207 | * Methods and properties of class 208 | */ 209 | _.extend(VerticalTimeline.prototype, { 210 | // Events 211 | events: { 212 | 'vt.build': ['buildLayout'], 213 | 'vt.layoutBuilt': ['getData'], 214 | 'vt.gotData': ['parseData'], 215 | 'vt.parsedData': ['buildTimeline'], 216 | 'vt.builtTimeline': ['loadImages', 'adjustWidth'], 217 | 'vt.loadedImages': ['isotopeIt'], 218 | 'vt.isotopized': ['adjustWidth', 'adjustSpine', 'domEvents'] 219 | }, 220 | 221 | // Event delegation 222 | loadEvents: function() { 223 | _.each(this.events, function(ea, ename) { 224 | _.each(ea, function(ehandler) { 225 | if (_.isFunction(this[ehandler])) { 226 | this.$el.on(ename, _.bind(this[ehandler], this)); 227 | } 228 | }, this); 229 | }, this); 230 | }, 231 | 232 | // Initial building 233 | buildLayout: function() { 234 | // Add base class for styling 235 | this.$el.addClass('vertical-timeline-container'); 236 | 237 | // Add template layout 238 | this.$el.html(this.templates.buttons({ 239 | data: this.options 240 | }) + this.templates.loading({})); 241 | 242 | // Get data 243 | this.$el.trigger('vt.layoutBuilt'); 244 | }, 245 | 246 | // Get data. Data can be from from Google Spreadsheet or JSON 247 | getData: function() { 248 | var thisVT = this; 249 | 250 | // Check if data is set and has data 251 | if (_.isArray(this.options.data) && this.options.data.length > 0) { 252 | this.data = this.options.data; 253 | this.$el.trigger('vt.gotData'); 254 | } 255 | else { 256 | Tabletop.init(_.extend({}, this.options.tabletopOptions, { 257 | key: this.options.key, 258 | wanted: [this.options.sheetName], 259 | callback: function(data, tabletop) { 260 | thisVT.data = data[thisVT.options.sheetName].elements; 261 | thisVT.tabletop = tabletop; 262 | thisVT.$el.trigger('vt.gotData'); 263 | } 264 | })); 265 | } 266 | }, 267 | 268 | // Process data 269 | parseData: function() { 270 | // Placeholder for groups 271 | this.groups = this.groups || {}; 272 | 273 | // Go through each row 274 | this.data = _.map(this.data, function(row) { 275 | // Column mapping. 276 | _.each(this.options.columnMapping, function(column, key) { 277 | if (!_.isUndefined(row[column])) { 278 | row[key] = row[column]; 279 | } 280 | }); 281 | 282 | // Parse date with moment 283 | row.date = moment(row.date, this.options.dateParse); 284 | row.timestamp = row.date.unix(); 285 | 286 | // Process into group 287 | this.groups = this.options.groupFunction(row, this.groups, this.options.defaultDirection); 288 | 289 | return row; 290 | }, this); 291 | 292 | // Trigger done 293 | this.$el.trigger('vt.parsedData'); 294 | }, 295 | 296 | // Build timline 297 | buildTimeline: function() { 298 | this.$el.append(this.templates.timeline({ 299 | data: { 300 | posts: _.map(this.data, function(d, di) { 301 | return this.templates.post({ 302 | data: d, 303 | options: this.options 304 | }); 305 | }, this).join(' '), 306 | groups: _.map(this.groups, function(g, gi) { 307 | return this.templates.group({ 308 | data: g 309 | }); 310 | }, this).join(' ') 311 | } 312 | })); 313 | 314 | this.$timeline = this.$el.find('.vertical-timeline-timeline'); 315 | this.$el.trigger('vt.builtTimeline'); 316 | }, 317 | 318 | // Wait for images to be loaded 319 | loadImages: function() { 320 | this.$el.imagesLoaded(_.bind(function() { 321 | this.$el.find('.loading').slideUp(); 322 | this.$timeline.fadeIn('fast', _.bind(function() { 323 | this.$el.trigger('vt.loadedImages'); 324 | }, this)); 325 | }, this)); 326 | }, 327 | 328 | // Make isotope layout 329 | isotopeIt: function() { 330 | this.$el.find('.vertical-timeline-timeline').isotope({ 331 | itemSelector: '.item', 332 | transformsEnabled: true, 333 | layoutMode: 'spineAlign', 334 | spineAlign:{ 335 | gutterWidth: this.options.gutterWidth 336 | }, 337 | getSortData: { 338 | timestamp: function($el) { 339 | return parseFloat($el.data('timestamp')); 340 | } 341 | }, 342 | sortBy: 'timestamp', 343 | sortAscending: (this.options.defaultDirection === 'newest') ? false : true, 344 | itemPositionDataEnabled: true, 345 | onLayout: _.bind(function($els, instance) { 346 | this.$el.trigger('vt.isotopized'); 347 | }, this), 348 | containerStyle: { 349 | position: 'relative' 350 | } 351 | }); 352 | }, 353 | 354 | // Adjust width of timeline 355 | adjustWidth: function() { 356 | var w = this.options.width; 357 | var containerW = this.$el.width(); 358 | var timelineW; 359 | var postW; 360 | 361 | if (w === 'auto') { 362 | w = containerW + 'px'; 363 | } 364 | 365 | // Set timeline width 366 | this.$timeline.css('width', w); 367 | timelineW = this.$timeline.width(); 368 | 369 | // Set width on posts 370 | postW = (timelineW / 2) - (this.options.gutterWidth / 2) - 3; 371 | this.$timeline.find('.post').width(postW); 372 | }, 373 | 374 | // Adjust the middle line 375 | adjustSpine: function() { 376 | var $lastItem = this.$el.find('.item.last'); 377 | var itemPosition = $lastItem.data('isotope-item-position'); 378 | var dateHeight = $lastItem.find('.date').height(); 379 | var dateOffset = $lastItem.find('.date').position(); 380 | var innerMargin = parseInt($lastItem.find('.inner').css('marginTop'), 10); 381 | var top = (dateOffset === undefined) ? 0 : parseInt(dateOffset.top, 10); 382 | var y = (itemPosition && itemPosition.y) ? 383 | parseInt(itemPosition.y, 10) : 0; 384 | var lineHeight = y + innerMargin + top + (dateHeight / 2); 385 | var $line = this.$el.find('.line'); 386 | var xOffset = (this.$timeline.width() / 2) - ($line.width() / 2); 387 | 388 | $line.height(lineHeight) 389 | .css('left', xOffset + 'px'); 390 | }, 391 | 392 | // DOM event 393 | domEvents: function() { 394 | if (this.domEventsAdded) { 395 | this.$el.trigger('vt.domEventsAdded'); 396 | return; 397 | } 398 | 399 | // Handle click of open close buttons on post 400 | this.$el.find('.item a.open-close').on('click', _.bind(function(e) { 401 | e.preventDefault(); 402 | var $thisButton = $(e.currentTarget); 403 | var $post = $thisButton.parents('.post'); 404 | var direction = ($post.hasClass('collapsed')) ? 'slideDown' : 'slideUp'; 405 | 406 | // Slide body 407 | $thisButton.siblings('.body')[direction](_.bind(function() { 408 | // Mark post and poke isotope 409 | $post.toggleClass('collapsed').toggleClass('expanded'); 410 | this.$timeline.isotope('reLayout'); 411 | }, this)); 412 | // Change top buttons 413 | this.$el.find('.expand-collapse-buttons a').removeClass('active'); 414 | }, this)); 415 | 416 | // Handle expand/collapse buttons 417 | this.$el.find('.vertical-timeline-buttons a.expand-all').on('click', _.bind(function(e) { 418 | e.preventDefault(); 419 | var $this = $(e.currentTarget); 420 | var thisVT = this; 421 | 422 | this.$el.find('.post .body').slideDown(function() { 423 | thisVT.$timeline.isotope('reLayout'); 424 | }); 425 | this.$el.find('.post').removeClass('collapsed').addClass('expanded'); 426 | this.$el.find('.expand-collapse-buttons a').removeClass('active'); 427 | $this.addClass('active'); 428 | }, this)); 429 | this.$el.find('.vertical-timeline-buttons a.collapse-all').on('click', _.bind(function(e) { 430 | e.preventDefault(); 431 | var $this = $(e.currentTarget); 432 | var thisVT = this; 433 | 434 | this.$el.find('.post .body').slideUp(function() { 435 | thisVT.$timeline.isotope('reLayout'); 436 | }); 437 | this.$el.find('.post').addClass('collapsed').removeClass('expanded'); 438 | this.$el.find('.expand-collapse-buttons a').removeClass('active'); 439 | $this.addClass('active'); 440 | }, this)); 441 | 442 | // Sorting buttons 443 | this.$el.find('.sort-buttons a').on('click', _.bind(function(e) { 444 | e.preventDefault(); 445 | var $this = $(e.currentTarget); 446 | 447 | // Don't proceed if already selected 448 | if ($this.hasClass('active')) { 449 | return false; 450 | } 451 | 452 | // Mark buttons 453 | this.$el.find('.sort-buttons a').removeClass('active'); 454 | $this.addClass('active'); 455 | 456 | // Change sorting 457 | if ($this.hasClass('sort-newest')) { 458 | this.updateGroups('newest'); 459 | } 460 | else { 461 | this.updateGroups('oldest'); 462 | } 463 | }, this)); 464 | 465 | // If jQuery resize plugin is enabled and the option is 466 | // enabled then handle resize 467 | if (this.options.handleResize === true && _.isFunction(this.$el.resize)) { 468 | this.$el.resize(_.throttle(_.bind(function() { 469 | this.$el.trigger('vt.isotopized'); 470 | }, this), 200)); 471 | } 472 | 473 | // Only need to add these once 474 | this.domEventsAdded = true; 475 | this.$el.trigger('vt.domEventsAdded'); 476 | }, 477 | 478 | // Updates group markers with the appropriate timestamp 479 | // for isotope layout 480 | updateGroups: function(direction) { 481 | var thisVT = this; 482 | direction = direction || this.options.defaultDirection; 483 | 484 | this.$el.find('.group-marker').each(function() { 485 | var $this = $(this); 486 | var timestamp = (direction !== 'newest') ? 487 | thisVT.groups[$this.data('id')].timestampStart : 488 | thisVT.groups[$this.data('id')].timestampEnd; 489 | $this.data('timestamp', timestamp); 490 | }); 491 | 492 | // Poke isotope 493 | this.$timeline.isotope('reloadItems') 494 | .isotope({ sortAscending: (direction !== 'newest') }); 495 | } 496 | }); 497 | 498 | 499 | /** 500 | * Extend Isotope for custom layout: spineAlign 501 | */ 502 | _.extend($.Isotope.prototype, { 503 | _spineAlignReset: function() { 504 | this.spineAlign = { 505 | colA: 0, 506 | colB: 0, 507 | lastY: -60 508 | }; 509 | }, 510 | 511 | _spineAlignLayout: function( $elems ) { 512 | var instance = this, 513 | props = this.spineAlign, 514 | gutterWidth = Math.round( this.options.spineAlign && this.options.spineAlign.gutterWidth ) || 0, 515 | centerX = Math.round(this.element.width() / 2); 516 | 517 | $elems.each(function(i, val) { 518 | var $this = $(this); 519 | var x, y; 520 | 521 | $this.removeClass('last').removeClass('top'); 522 | if (i == $elems.length - 1) { 523 | $this.addClass('last'); 524 | } 525 | if ($this.hasClass('group-marker')) { 526 | var width = $this.width(); 527 | x = centerX - (width / 2); 528 | if (props.colA >= props.colB) { 529 | y = props.colA; 530 | if (y === 0) { 531 | $this.addClass('top'); 532 | } 533 | props.colA += $this.outerHeight(true); 534 | props.colB = props.colA; 535 | } 536 | else { 537 | y = props.colB; 538 | if (y === 0) { 539 | $this.addClass('top'); 540 | } 541 | props.colB += $this.outerHeight(true); 542 | props.colA = props.colB; 543 | } 544 | } 545 | else { 546 | $this.removeClass('left').removeClass('right'); 547 | var isColA = props.colB >= props.colA; 548 | if (isColA) { 549 | $this.addClass('left'); 550 | } 551 | else { 552 | $this.addClass('right'); 553 | } 554 | 555 | x = isColA ? 556 | centerX - ( $this.outerWidth(true) + gutterWidth / 2 ) : // left side 557 | centerX + (gutterWidth / 2); // right side 558 | y = isColA ? props.colA : props.colB; 559 | if (y - props.lastY <= 60) { 560 | var extraSpacing = 60 - Math.abs(y - props.lastY); 561 | $this.find('.inner').css('marginTop', extraSpacing); 562 | props.lastY = y + extraSpacing; 563 | } 564 | else { 565 | $this.find('.inner').css('marginTop', 0); 566 | props.lastY = y; 567 | } 568 | props[( isColA ? 'colA' : 'colB' )] += $this.outerHeight(true); 569 | } 570 | instance._pushPosition( $this, x, y ); 571 | }); 572 | }, 573 | 574 | _spineAlignGetContainerSize: function() { 575 | var size = {}; 576 | size.height = this.spineAlign[( this.spineAlign.colB > this.spineAlign.colA ? 'colB' : 'colA' )]; 577 | return size; 578 | }, 579 | 580 | _spineAlignResizeChanged: function() { 581 | return true; 582 | } 583 | }); 584 | 585 | /** 586 | * Turn verticalTimeline into jQuery plugin 587 | */ 588 | $.fn.verticalTimeline = function(options) { 589 | return this.each(function() { 590 | if (!$.data(this, 'verticalTimeline')) { 591 | $.data(this, 'verticalTimeline', new VerticalTimeline(this, options)); 592 | } 593 | }); 594 | }; 595 | 596 | // Incase someone wants to use the base class, return it 597 | return VerticalTimeline; 598 | 599 | }); 600 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-vertical-timeline", 3 | "version": "0.3.0", 4 | "homepage": "https://github.com/MinnPost/jquery-vertical-timeline", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/minnpost/jquery-vertical-timeline" 8 | }, 9 | "bugs": "https://github.com/minnpost/jquery-vertical-timeline/issues", 10 | "license": "MIT", 11 | "author": { 12 | "name": "MinnPost", 13 | "email": "data@minnpost.com" 14 | }, 15 | "dependencies": {}, 16 | "devDependencies": { 17 | "grunt": "~0.4.1", 18 | "grunt-contrib-concat": "~0.3.0", 19 | "grunt-contrib-jshint": "~0.8.0", 20 | "grunt-contrib-uglify": "~0.3.1", 21 | "grunt-contrib-copy": "~0.5.0", 22 | "grunt-contrib-clean": "~0.5.0", 23 | "grunt-contrib-watch": "~0.5.3", 24 | "grunt-contrib-connect": "~0.6.0", 25 | "grunt-contrib-requirejs": "~0.4.0", 26 | "grunt-contrib-cssmin": "~0.7.0", 27 | "grunt-contrib-compass": "~0.7.0", 28 | "grunt-text-replace": "~0.3.10" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /styles/styles.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS for timeline-jquery. 3 | */ 4 | 5 | @import "compass"; 6 | 7 | // Variables 8 | $font-sans-serif: Georgia, sans-serif; 9 | 10 | 11 | // Mixins 12 | @mixin inline-block() { 13 | display: -moz-inline-stack; 14 | display: inline-block; 15 | zoom: 1; 16 | *display: inline; 17 | } 18 | 19 | @mixin clearfix() { 20 | *zoom: 1; 21 | 22 | &:before, 23 | &:after { 24 | content: " "; 25 | display: table; 26 | } 27 | 28 | &:after { 29 | clear: both; 30 | } 31 | } 32 | 33 | // Styles 34 | .vertical-timeline-container { 35 | @include clearfix(); 36 | 37 | .vertical-timeline-timeline { 38 | width: 600px; 39 | margin: 20px auto 30px auto; 40 | 41 | // Will show when images are loaded 42 | display: none; 43 | } 44 | 45 | .clearfix { 46 | @include clearfix(); 47 | } 48 | 49 | .loading { 50 | text-align: center; 51 | font-size: 2em; 52 | margin: 1em; 53 | color: #565656; 54 | } 55 | 56 | 57 | /** 58 | * Posts 59 | */ 60 | .post { 61 | width: 271px; 62 | margin: 0 0 10px 0; 63 | float: left; 64 | 65 | &.last { 66 | margin-bottom: 0; 67 | } 68 | 69 | .inner { 70 | position: relative; 71 | padding: 11px; 72 | border: 1px #adadad solid; 73 | background-color: #fff; 74 | min-height: 37px; 75 | word-wrap: break-word; 76 | @include border-radius(2px); 77 | @include box-shadow(0px 1px 1px 0px #ADADAD); 78 | } 79 | 80 | &.right { 81 | float: right; 82 | } 83 | 84 | .timestamp { 85 | display: none; 86 | } 87 | 88 | h3 { 89 | margin: 0; 90 | font-family: $font-sans-serif; 91 | font-size: 20px; 92 | font-weight: normal; 93 | color: #010101; 94 | padding-right: 30px; 95 | line-height: 1.3em; 96 | } 97 | 98 | .caption { 99 | color: #9d9d9d; 100 | font-family: $font-sans-serif; 101 | font-size: 12px; 102 | margin-top: 4px; 103 | } 104 | 105 | .body { 106 | margin-top: 10px; 107 | } 108 | 109 | &.collapsed .body { 110 | display: none; 111 | } 112 | &.expanded .body { 113 | display: block; 114 | } 115 | 116 | .body img { 117 | max-width: 100%; 118 | } 119 | 120 | .text { 121 | color: #393939; 122 | font-family: Georgia, sans-serif; 123 | font-size: 15px; 124 | margin: 10px 0; 125 | line-height: 1.5; 126 | } 127 | 128 | a.open-close { 129 | background: transparent image-url("button-up-down-arrow.png") no-repeat; 130 | height: 17px; 131 | width: 16px; 132 | position: absolute; 133 | right: 11px; 134 | top: 11px; 135 | } 136 | 137 | &.collapsed a.open-close { 138 | background-position: left bottom; 139 | } 140 | 141 | .title .title-icon { 142 | height: 20px; 143 | width: 20px; 144 | margin-right: 4px; 145 | vertical-align: top; 146 | } 147 | 148 | &.closed .title { 149 | display: table; 150 | min-height: 40px; 151 | 152 | h3 { 153 | display: table-cell; 154 | vertical-align: middle; 155 | } 156 | } 157 | 158 | a.more { 159 | color: #676767; 160 | text-decoration: none; 161 | text-transform: uppercase; 162 | font-size: 12px; 163 | font-weight: bold; 164 | text-align: center; 165 | padding: 4px 15px; 166 | margin: 5px auto 0 auto; 167 | border: 1px #BDBDBD solid; 168 | display: block; 169 | line-height: 28px; 170 | @include border-radius(2px); 171 | @include box-shadow(0px 1px 1px 0px #BDBDBD); 172 | } 173 | 174 | a.more:hover { 175 | background-color: #FAFAFA; 176 | } 177 | 178 | .date { 179 | color: #6a6a6a; 180 | font-size: 11px; 181 | text-align: center; 182 | position: absolute; 183 | top: 19px; 184 | display: block; 185 | width: 54px; 186 | height: 21px; 187 | line-height: 20px; 188 | } 189 | 190 | &.left .date { 191 | background: transparent image-url("tab-left.png") no-repeat right; 192 | right: -54px; 193 | } 194 | 195 | &.right .date { 196 | background: transparent image-url("tab-right.png") no-repeat left; 197 | left: -54px; 198 | } 199 | } 200 | 201 | 202 | /** 203 | * Group marker 204 | */ 205 | .group-marker { 206 | float: left; 207 | width: 80px; 208 | margin: 50px 0 15px 0; 209 | 210 | .timestamp { 211 | display: none; 212 | } 213 | 214 | &.top { 215 | margin-top: 0; 216 | } 217 | 218 | .inner { 219 | width: 76px; 220 | height: 26px; 221 | padding: 2px; 222 | background-color: #FFF; 223 | } 224 | 225 | .inner2 { 226 | background-color: #434a50; 227 | @include border-radius(2px); 228 | } 229 | } 230 | 231 | 232 | /** 233 | * Group 234 | */ 235 | .group { 236 | text-align: center; 237 | color: #fff; 238 | font-size: 14px; 239 | font-weight: bold; 240 | height: 26px; 241 | line-height: 26px; 242 | } 243 | 244 | /** 245 | * Top buttons 246 | */ 247 | .vertical-timeline-buttons { 248 | text-align: center; 249 | margin: 20px 0 10px 0; 250 | 251 | div { 252 | @include inline-block(); 253 | } 254 | 255 | .expand-collapse-buttons { 256 | margin-right: 20px; 257 | } 258 | 259 | a { 260 | @include inline-block(); 261 | width: 126px; 262 | border: 1px #adadad solid; 263 | @include border-radius(2px); 264 | font-size: 12px; 265 | font-weight: bold; 266 | text-decoration: none; 267 | padding: 10px 12px; 268 | background-color: #FFF; 269 | color: #3b3b3b; 270 | text-transform: uppercase; 271 | } 272 | 273 | a.active:hover, 274 | a.active { 275 | background-color: #f6f6f6; 276 | color: #a9a9a9; 277 | cursor: default; 278 | @include box-shadow(none); 279 | } 280 | 281 | a:hover { 282 | @include box-shadow(0px 1px 1px 0px #adadad); 283 | } 284 | 285 | a span { 286 | padding-right: 15px; 287 | background-position: right -20px; 288 | } 289 | 290 | a.active span { 291 | background-position: right 4px; 292 | } 293 | 294 | a.expand-all span, 295 | a.sort-newest span { 296 | background-image: image-url("down-arrows.png"); 297 | background-repeat: no-repeat; 298 | } 299 | 300 | a.collapse-all span, 301 | a.sort-oldest span { 302 | background-image: image-url("up-arrows.png"); 303 | background-repeat: no-repeat; 304 | } 305 | 306 | 307 | @media (max-width: 675px) { 308 | 309 | .expand-collapse-buttons, 310 | .sort-buttons { 311 | margin: 0 0 10px 0; 312 | } 313 | } 314 | } 315 | 316 | 317 | /** 318 | * Layout 319 | */ 320 | .line-container { 321 | width: 4px; 322 | text-align: center; 323 | margin: 0 auto; 324 | display: block; 325 | } 326 | 327 | .isotope .line { 328 | margin: 0 auto; 329 | background-color: #b3b6b8; 330 | display: block; 331 | float: left; 332 | height: 100%; 333 | left: 298px; 334 | width: 4px; 335 | position: absolute; 336 | } 337 | 338 | /** 339 | * Isotope Filtering 340 | */ 341 | .isotope-item { 342 | z-index: 2; 343 | } 344 | 345 | .isotope-hidden.isotope-item { 346 | pointer-events: none; 347 | z-index: 1; 348 | } 349 | 350 | /** 351 | * Isotope CSS3 transitions 352 | */ 353 | .vertical-timeline-container .isotope, 354 | .vertical-timeline-container .isotope .isotope-item { 355 | @include transition-duration(0.8s); 356 | } 357 | 358 | .vertical-timeline-container .isotope { 359 | @include transition-property(height, width); 360 | } 361 | 362 | .vertical-timeline-container .isotope .isotope-item { 363 | -webkit-transition-property: -webkit-transform, opacity; 364 | -moz-transition-property: -moz-transform, opacity; 365 | -ms-transition-property: -ms-transform, opacity; 366 | -o-transition-property: top, left, opacity; 367 | transition-property: transform, opacity; 368 | } 369 | 370 | /** 371 | * disabling Isotope CSS3 transitions 372 | */ 373 | .isotope.no-transition, 374 | .isotope.no-transition .isotope-item, 375 | .isotope .isotope-item.no-transition { 376 | @include transition-duration(0s); 377 | } 378 | } 379 | --------------------------------------------------------------------------------