├── .gitignore
├── stylesheets
└── style.css
├── karma.conf.js
├── package.json
├── spec.html
├── license.txt
├── README.md
├── example
└── example.html
├── Gruntfile.js
├── spec.js
└── es2015-src
└── jqtipnav.es6.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .sass-cache/
3 | *.map
4 | js-compiled/
5 | node_modules/
6 | vendor/
--------------------------------------------------------------------------------
/stylesheets/style.css:
--------------------------------------------------------------------------------
1 | [data-jqtipnav] {
2 | background: #fff;
3 | padding: 0.4em;
4 | border: 1px solid black;
5 | }
6 | [data-jqtipnav] ul {
7 | list-style-type: none;
8 | }
9 | [data-jqtipnav] ul, [data-jqtipnav] ul li {
10 | margin: 0;
11 | padding: 0;
12 | display: inline;
13 | }
14 | [data-jqtipnav] ul li {
15 | font-size: 0.8em;
16 | padding: 3px 2px;
17 | }
18 | [data-jqtipnav] ul li a {
19 | text-decoration: none;
20 | color: inherit;
21 | }
22 | [data-jqtipnav] ul li a:visited {
23 | color: inherit;
24 | }
25 | [data-jqtipnav] ul li a:hover {
26 | text-decoration: underline;
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = function(config) {
2 | config.set({
3 | autoWatch: false,
4 | basePath: '',
5 | 'frameworks': ["jasmine"],
6 | 'files': [
7 | {pattern: 'vendor/jquery/jquery.js', watched: true, served: true, included: true},
8 | {pattern: 'node_modules/jasmine-jquery/lib/jasmine-jquery.js', watched: true, served: true, included: true},
9 | {pattern: 'vendor/lodash/lodash.js', watched: true, served: true, included: true},
10 | {pattern: 'js-compiled/jqtipnav.es6-compiled.js', watched: true, served: true, included: true},
11 | {pattern: 'spec.html', watched: true, served: true, included: true},
12 | {pattern: 'spec.js', watched: true, served: true, included: true}
13 | ]
14 | });
15 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jQTipnav",
3 | "version": "1.0.1",
4 | "description": "jQuery plugin which enables showing small navigation bars on interaction with action element",
5 | "homepage": "http://tvrtkom.github.io/jQTipnav/",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/TvrtkoM/jQTipnav.git"
9 | },
10 | "bugs": "https://github.com/TvrtkoM/jQTipnav/issues",
11 | "main": "build/jqtipnav.min.js",
12 | "license": "BSD-3-Clause",
13 | "author": {
14 | "email": "tvrtkom@gmail.com",
15 | "name": "TvrtkoM"
16 | },
17 | "devDependencies": {
18 | "babel-cli": "~6.2.0",
19 | "babel-preset-es2015": "~6.1.18",
20 | "grunt": "0.4.5",
21 | "grunt-babel": "~6.0.0",
22 | "grunt-contrib-cssmin": "~0.14.0",
23 | "grunt-contrib-uglify": "~0.11.0",
24 | "grunt-contrib-watch": "^0.6.1",
25 | "grunt-karma": "0.12.1",
26 | "grunt-lodash": "~0.5.0",
27 | "jasmine": "2.3.2",
28 | "jasmine-jquery": "2.1.1",
29 | "jquery": "^2.2.0",
30 | "karma": "^0.13.19",
31 | "karma-firefox-launcher": "0.1.6",
32 | "karma-jasmine": "0.3.6",
33 | "lodash-cli": "~3.10.1",
34 | "skeleton-css": "^2.0.4"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/spec.html:
--------------------------------------------------------------------------------
1 |
26 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
44 |
49 |
53 |
56 |
59 |
--------------------------------------------------------------------------------
/license.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, Tvrtko Majstorović
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 | * Redistributions of source code must retain the above copyright
7 | notice, this list of conditions and the following disclaimer.
8 | * Redistributions in binary form must reproduce the above copyright
9 | notice, this list of conditions and the following disclaimer in the
10 | documentation and/or other materials provided with the distribution.
11 | * Neither the name of the nor the
12 | names of its contributors may be used to endorse or promote products
13 | derived from this software without specific prior written permission.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [jQTipnav](http://tvrtkom.github.io/jQTipnav)
2 |
3 | ## what is this?
4 |
5 | > A jQuery plugin which enables showing small navigation bars on interaction with action element.
6 |
7 | ## usage
8 |
9 | Write your HTML:
10 |
11 |
12 |
13 |
14 |
19 |
20 | Hover over!
21 |
22 |
23 |
24 |
25 |
26 | Initialize the plugin with:
27 |
28 | $('#subnav').tipnav(options);
29 |
30 | where options is an object containing at least *action_element* property.
31 | If multiple navigation lists are matched inside jquery selector, plugin will show additional arrows for navigation.
32 | Clicking on arrow will show you next/previous navigation list.
33 |
34 | ## options
35 |
36 | * action_element - jQuery selector object (required)
37 | * fade_time - set fade-out time in ms
38 | * trigger_event - set to 'click' to trigger showing and hiding tipnav on mouse click instead hovering
39 |
40 | ## clean method
41 |
42 | Plugin can be called with string 'clean' as first argument. In this case it will try to revert back to state before plugin is initialized,
43 | removing all created html elements.
44 |
45 | e.g
46 |
47 | $(document).tipnav('clean') // removes all jqtipnav elements from page and shows hidden ones
48 | $('#subnav').tipnav('clean') // removes all jqtipnv elements but only for matched element (#subnav)
49 |
50 | ## contributing
51 |
52 | Have some ideas to make this plugin work better or want to add a feature? Following commands and grunt tasks will get you going:
53 |
54 | * npm install - install npm development dependencies
55 | * grunt build - run build-dep task, compile es2015 code, copy minified js and css files to *build* directory
56 | * grunt build-dep - build and copy dependencies
57 | * grunt build-bin - compile es2015 code and copy it minified to *build* directory
58 | * grunt babel - compile es2015 code to es5 inside *js-compiled* directory
59 | * grunt karma - run tests
60 | * grunt watch - auto compile and test code when files change
61 |
62 | After running *grunt build*, example/example.html can be used to see plugin in action.
63 | Test cases in spec.js are done by using Jasmine. If contributing please write test cases for your additions.
64 |
--------------------------------------------------------------------------------
/example/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | jQTipNav examples
6 |
7 |
8 |
9 |
13 |
14 |
15 |
16 |
jQTipnav
17 |
18 | A jQuery plugin which enables showing small navigation bars on interaction with action element.
19 |
20 |
21 | Usage:
22 |
23 |
$element.tipnav(options);
24 |
25 | where $element is jquery object pointing to unordered list elements - 'ul' tags. Options must contain 'action_element' jquery object.
26 |
27 |
28 | jQTipnav applied to single element:
29 |
30 |
31 |
32 |
$('ul#subnav').tipnav({ 'action_el': 'a#link1' });
33 |
34 |
39 |
40 |
41 | jQTipnav applied to multiple elements:
42 |
43 |
44 |
45 |
$('ul.subnav').tipnav({ 'action_el': 'a#link1' });
46 |
47 |
52 |
53 |
Additional options:
54 |
55 | fade_time ->
56 | fade out time in milliseconds
57 | trigger_event ->
58 | use 'click' event to trigger navigation bar visibility
59 |
60 |
61 |
66 |
71 |
76 |
80 |
81 |
82 |
83 |
91 |
92 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = function(grunt) {
4 | 'use strict';
5 |
6 | // copy recursively files from 'src' directory to 'dest' dir under vendor
7 | var copyToVendor = function(src, dest) {
8 | grunt.file.recurse(src, function(abs, root, sub, filename) {
9 | var d = path.join('vendor', dest, sub || '', filename);
10 | grunt.file.copy(abs, d, {
11 | process: function(file, src) {
12 | if(grunt.file.exists(d)) {
13 | return false;
14 | }
15 | grunt.log.writeln('copying: ' + src + ' to ' + d);
16 | return file;
17 | }
18 | });
19 | });
20 | };
21 |
22 | grunt.initConfig({
23 | watch: {
24 | js: {
25 | options: {
26 | interrupt: true
27 | },
28 | files: ['es2015-src/*'],
29 | tasks: ['build-dep', 'babel', 'build-bin', 'karma:dev']
30 | }
31 | },
32 | karma: {
33 | options: {
34 | configFile: 'karma.conf.js',
35 | browsers: ['Firefox'],
36 | singleRun: true
37 | },
38 | dev: {
39 | reporters: 'dots'
40 | }
41 | },
42 | lodash: {
43 | build: {
44 | dest: 'vendor/lodash/lodash.js',
45 | options: {
46 | category: ['collection', 'function']
47 | }
48 | }
49 | },
50 | babel: {
51 | build: {
52 | options: {
53 | presets: 'es2015'
54 | },
55 | files: [{
56 | expand: true,
57 | cwd: 'es2015-src',
58 | src: ['*'],
59 | dest: 'js-compiled',
60 | ext: '.es6-compiled.js'
61 | }]
62 | }
63 | },
64 | uglify: {
65 | options: {
66 | mangle: true
67 | },
68 | jqtipnav: {
69 | files: {
70 | 'build/jqtipnav.min.js': ['js-compiled/jqtipnav.es6-compiled.js']
71 | }
72 | }
73 | },
74 | cssmin: {
75 | target: {
76 | files: {
77 | 'build/jqtipnav.min.css': ['stylesheets/style.css']
78 | }
79 | }
80 | }
81 | });
82 | grunt.loadNpmTasks('grunt-contrib-uglify');
83 | grunt.loadNpmTasks('grunt-contrib-cssmin');
84 | grunt.loadNpmTasks('grunt-contrib-watch');
85 | grunt.loadNpmTasks('grunt-karma');
86 | grunt.loadNpmTasks('grunt-babel');
87 | grunt.loadNpmTasks('grunt-lodash');
88 |
89 | // prepares project dependencies - copy jquery and lodash to vendor directory
90 | grunt.registerTask('build-dep', 'prepare development dependencies', function() {
91 | if(!grunt.file.exists('vendor/lodash/lodash.js') || !grunt.file.exists('vendor/lodash/lodash.min.js')) {
92 | grunt.task.run('lodash');
93 | }
94 |
95 | copyToVendor('node_modules/skeleton-css/css', 'skeleton');
96 | copyToVendor('node_modules/jquery/dist', 'jquery');
97 | });
98 |
99 | // build javascript binary in build/ directory
100 | grunt.registerTask('build-bin', ['babel', 'uglify:jqtipnav']);
101 |
102 | // build all the development and binary files (js & css)
103 | grunt.registerTask('build', ['build-dep', 'babel', 'uglify:jqtipnav', 'cssmin']);
104 | };
--------------------------------------------------------------------------------
/spec.js:
--------------------------------------------------------------------------------
1 | jasmine.getFixtures().fixturesPath = '/base/';
2 |
3 | var get_id = function($selector) {
4 | var id;
5 | _.forEach(['', '-main'], function(val) {
6 | id = $selector.attr('data-jqtipnav' + val);
7 | });
8 | return id;
9 | };
10 |
11 | describe('main application', function() {
12 | var $test_container_01, $notes, tipnav_func = $.fn.tipnav,
13 | default_options;
14 |
15 | beforeEach(function() {
16 | loadFixtures('spec.html');
17 | default_options = {
18 | 'action_element': $('#link-01')
19 | };
20 | $test_container_01 = $('#test-container-01');
21 | $notes = $('.note');
22 | $test_container_01.tipnav(default_options);
23 | });
24 |
25 | it('should be present as jquery function', function() {
26 | spyOn($.fn, 'tipnav');
27 | $('').tipnav(default_options);
28 | expect($.fn.tipnav).toHaveBeenCalled();
29 | });
30 |
31 | it('should wrap multiple tipnavs', function() {
32 | $notes.tipnav(default_options);
33 | expect($('.jqtipnav-wrap')).toExist();
34 | expect($('.jqtipnav-wrap')).toBeInDOM();
35 | expect($('.jqtipnav-wrap').find('*').length).toBeGreaterThan(0);
36 | });
37 |
38 | it('should turn original elements unvisible', function() {
39 | expect($test_container_01).toBeHidden();
40 | $notes.tipnav(default_options);
41 | expect($notes).toBeHidden();
42 | });
43 |
44 | it('should put div[data-jqtipbar] element into body when initialized', function() {
45 | expect($('body > div[data-jqtipnav]')).toExist();
46 | expect($('body > div[data-jqtipnav]')).toBeInDOM();
47 | });
48 |
49 |
50 | it('should have cleanup mechanism for all created elements', function() {
51 | // calling 'clean' on document to delete all created elements
52 | $(document).tipnav('clean');
53 | expect($('body > div[data-jqtipnav]').length).toBe(0);
54 | $notes.tipnav(default_options);
55 | $test_container_01.tipnav(default_options);
56 | expect($('.jqtipnav-wrap > div[data-jqtipnav]').length).toBe(4);
57 | expect($('body > div[data-jqtipnav]').length).toBe(1);
58 | $(document).tipnav('clean');
59 | });
60 |
61 | it('should create each element with different data-jqtipnav value', function() {
62 | var ids = [], id;
63 | $(document).tipnav('clean');
64 | $notes.tipnav(default_options);
65 | $test_container_01.tipnav(default_options);
66 | for(var i=0; i<$('body > div[data-jqtipnav]').length; i++) {
67 | id = $('body > div[data-jqtipnav]').eq(i).data('jqtipnav');
68 | expect(_.contains(ids, id)).toBe(false);
69 | ids.push(id);
70 | }
71 | });
72 |
73 | it('should add data-jqtipnav-main attribute on elements plugin is applied', function() {
74 | expect($test_container_01.data('jqtipnav-main')).toBeDefined();
75 | });
76 |
77 | it('should delete created elements for particular selector when calling "clean"', function() {
78 | var ids = [], $test_els;
79 | $(document).tipnav('clean');
80 | $notes.tipnav(default_options);
81 | $test_els = $('div[data-jqtipnav-main]');
82 | for(var i=0; i<$test_els.length; i++) {
83 | ids.push($test_els.eq(i).data('jqtipnav-main'));
84 | }
85 | $test_container_01.tipnav(default_options);
86 | $notes.tipnav('clean');
87 | for(var i=0; i
div[data-jqtipnav]').data('jqtipnav')).toBeDefined();
125 | });
126 |
127 | it('should hide created elements as well', function() {
128 | $(document).tipnav('clean');
129 | $test_container_01.tipnav(default_options);
130 | $notes.tipnav(default_options);
131 | expect($('div[data-jqtipnav]')).toBeHidden();
132 | $notes.tipnav('clean');
133 | expect($notes).toBeVisible();
134 | $(document).tipnav('clean');
135 | });
136 |
137 | it('should copy main elements content to tipnav elements', function() {
138 | var id = $test_container_01.data('jqtipnavMain');
139 | expect($('div[data-jqtipnav=' + id + ']').find('ul').html()).toEqual($test_container_01.html().trim());
140 | });
141 | });
142 |
143 | describe('tipnav', function() {
144 | var $test_container_01, $notes,
145 | default_options, $action_el;
146 |
147 | beforeEach(function() {
148 | loadFixtures('spec.html');
149 | $action_el = $('#link-01');
150 | default_options = {
151 | 'action_element': $action_el
152 | };
153 | $(document).tipnav('clean');
154 | $test_container_01 = $('#test-container-01');
155 | $notes = $('.note');
156 | $test_container_01.tipnav(default_options);
157 | });
158 |
159 | afterEach(function() {
160 | });
161 |
162 | describe('positioning', function() {
163 | var $test_container_01,
164 | default_options, id,
165 | tipnav,
166 | $action1,
167 | dims,
168 | get_dimensions = function(tn, ac) {
169 | return {
170 | 'tn': {
171 | 'off': tn.offset(),
172 | 'height': tn.outerHeight(),
173 | 'width': tn.outerWidth()
174 | },
175 | 'ac': {
176 | 'off': ac.offset(),
177 | 'height': ac.outerHeight(),
178 | 'width': ac.outerWidth()
179 | }
180 | };
181 | };
182 |
183 | beforeEach(function() {
184 | loadFixtures('spec.html');
185 | $(document).tipnav('clean');
186 | $action1 = $('#link-01');
187 | $test_container_01 = $('#test-container-01');
188 | });
189 |
190 | it('should show in middle if the action element if smaller than it', function() {
191 | default_options = {
192 | 'action_element': $action1
193 | };
194 | $test_container_01.tipnav(default_options);
195 | id = get_id($test_container_01);
196 | tipnav = $('[data-jqtipnav=' + id + ']');
197 | $action1.trigger('mouseenter');
198 | dims = get_dimensions(tipnav, $action1);
199 | expect(tipnav).toBeVisible();
200 | expect(dims.ac.width).toBeGreaterThan(dims.tn.width);
201 | expect(Math.floor(dims.tn.off.left)).toEqual(Math.floor(dims.ac.off.left + dims.ac.width/2 - dims.tn.width/2));
202 | });
203 |
204 | });
205 | describe('containers', function() {
206 | describe('hovering over action', function () {
207 | var $action, $tipnavs,
208 | ids
209 | ;
210 | beforeEach(function () {
211 | loadFixtures('spec.html');
212 | ids = [];
213 | jQuery.fx.off = true;
214 | $action = $('#link-02');
215 | $tipnavs = $('.tipnav');
216 | $(document).tipnav('clean');
217 | $tipnavs.tipnav({
218 | 'action_element': $action
219 | });
220 | $('div[data-jqtipnav]').each(function () {
221 | ids.push($(this).attr('data-jqtipnav'));
222 | });
223 | });
224 |
225 | it('should show only first tipnav when activated', function () {
226 | $action.trigger('mouseenter');
227 | expect($('div[data-jqtipnav=' + ids[0] + ']')).toBeVisible();
228 | expect($('div[data-jqtipnav=' + ids[1] + ']')).toBeHidden();
229 | expect($('div[data-jqtipnav=' + ids[2] + ']')).toBeHidden();
230 | $action.trigger('mouseleave');
231 | });
232 |
233 | it('should cycle navs on arrow clicks', function() {
234 | var tipnav1 = $('div[data-jqtipnav=' + ids[0] + ']'),
235 | tipnav2 = $('div[data-jqtipnav=' + ids[1] + ']'),
236 | tipnav3 = $('div[data-jqtipnav=' + ids[2] + ']'),
237 | next_arr = tipnav1.find('.jqtipnav-next'),
238 | prev_arr = tipnav1.find('.jqtipnav-prev')
239 | ;
240 | $action.trigger('mouseenter');
241 | next_arr.click();
242 | expect(tipnav1).toBeHidden();
243 | expect(tipnav2).toBeVisible();
244 | expect(tipnav3).toBeHidden();
245 | next_arr = $(tipnav2).find('.jqtipnav-next');
246 | next_arr.trigger('click');
247 | expect(tipnav1).toBeHidden();
248 | expect(tipnav2).toBeHidden();
249 | expect(tipnav3).toBeVisible();
250 | next_arr = $(tipnav3).find('.jqtipnav-next');
251 | next_arr.trigger('click');
252 | expect(tipnav1).toBeVisible();
253 | expect(tipnav2).toBeHidden();
254 | expect(tipnav3).toBeHidden();
255 | prev_arr.click();
256 | expect(tipnav1).toBeHidden();
257 | expect(tipnav2).toBeHidden();
258 | expect(tipnav3).toBeVisible();
259 | $action.trigger('mouseleave');
260 | });
261 |
262 | });
263 | });
264 | });
265 |
266 |
--------------------------------------------------------------------------------
/es2015-src/jqtipnav.es6.js:
--------------------------------------------------------------------------------
1 | (function(factory) {
2 | if(typeof define === 'function' && define.amd) {
3 | define(['jquery'], factory);
4 | } else {
5 | factory(window.jQuery);
6 | }
7 | })(function ($) {
8 | var jQTipnav, _module,
9 | unique = (function () {
10 | // returns incremented numbers
11 | var id;
12 | return {
13 | generate: function () {
14 | id = typeof id === 'undefined' ? 1 : id + 1;
15 | return id;
16 | }
17 | };
18 | }())
19 | ;
20 |
21 | _module = class {
22 | define (name, _obj) {
23 | this[name] = _obj
24 | }
25 | };
26 |
27 | jQTipnav = new _module();
28 |
29 | jQTipnav.define('Action', class {
30 | constructor($action_el, options) {
31 | this.$el = $action_el;
32 | this.trigger_event = options.trigger_event;
33 | this.fade_time = options.fade_time;
34 | }
35 |
36 | get_geometry(container) {
37 | // returns container's and action element's dimensions and offset point
38 | return {
39 | 'tn': {
40 | 'off': container.offset(),
41 | 'height': container.outerHeight(),
42 | 'width': container.outerWidth()
43 | },
44 | 'ac': {
45 | 'off': this.$el.offset(),
46 | 'height': this.$el.outerHeight(),
47 | 'width': this.$el.outerWidth()
48 | }
49 | };
50 | }
51 |
52 | get_container_position(container) {
53 | // return position of the navigation container on the screen
54 | var geometry = this.get_geometry(container),
55 | full_width = $(window).width(),
56 | room = (geometry.tn.width - geometry.ac.width) / 2,
57 | ac_mid = geometry.ac.off.left + geometry.ac.width / 2,
58 | top = geometry.ac.off.top - geometry.tn.height,
59 | left
60 | ;
61 | if (geometry.tn.width <= geometry.ac.width || geometry.ac.off.left > room
62 | && full_width - (geometry.ac.off.left + geometry.ac.width) > room) {
63 | left = ac_mid - geometry.tn.width / 2;
64 | } else {
65 | if(full_width - (geometry.ac.off.left + geometry.ac.width) < room) {
66 | left = full_width - geometry.tn.width;
67 | } else {
68 | left = 0;
69 | }
70 | }
71 | if (geometry.ac.off.top < geometry.tn.height) {
72 | top = geometry.ac.off.top + geometry.ac.height;
73 | }
74 | return {
75 | 'left': left,
76 | 'top': top
77 | };
78 | }
79 |
80 | bind_container(container) {
81 | // sets up action element with basic events for displaying containers
82 | var set_hide_timeout = () => {
83 | // stores timeout to element's data attribute after
84 | // container fades out after timeout of 500
85 | container.data('hoverTimeout', setTimeout(() => {
86 | container.fadeOut(this.fade_time || 500, () => {
87 | container.data('open', false);
88 | })
89 | }, 200));
90 | }
91 | ;
92 | this.$el.bind(this.trigger_event, (e) => {
93 | // on specified trigger event container should appear
94 | e.preventDefault();
95 | container.show().css('left', 0);
96 | let css = this.get_container_position(container);
97 | container.data('open', true).css(css);
98 | // clear hover timeout each time element is triggered
99 | if (typeof container.data('hoverTimeout') !== 'undefined')
100 | clearTimeout(container.data('hoverTimeout'));
101 |
102 | if (this.trigger_event === 'click') {
103 | // start timeout if container visible and only on 'click'
104 | if (container.data('open') === true) {
105 | set_hide_timeout();
106 | }
107 | }
108 | });
109 | if (this.trigger_event !== 'click') {
110 | // runs only on 'hover'
111 | this.$el.mouseleave(function () {
112 | set_hide_timeout();
113 | });
114 | container.mouseenter(function () {
115 | if(container.data('fade') == true) {
116 | container.stop();
117 | container.fadeIn(100);
118 | container.data('fade', false);
119 | }
120 | clearTimeout($(this).data('hoverTimeout'));
121 | });
122 | container.mouseleave(function () {
123 | container.data('fade', true);
124 | set_hide_timeout();
125 | });
126 | }
127 | }
128 |
129 | setup_container(container, wrap) {
130 | // sets up container with additional events for navigation and positioning
131 | var css,
132 | arrows = container.find('.jqtipnav-prev, .jqtipnav-next')
133 | ;
134 | if(arrows.length > 0) {
135 | // show next visible element when arrows are clicked
136 | arrows.bind('click', (e) => {
137 | var $el, arrow = $(e.target);
138 | e.preventDefault();
139 | if (arrow.hasClass('jqtipnav-prev')) {
140 | $el = container.prev();
141 | if ($el.length == 0) {
142 | $el = container.siblings(':last');
143 | }
144 | }
145 | if (arrow.hasClass('jqtipnav-next')) {
146 | $el = container.next();
147 | if ($el.length == 0) {
148 | $el = container.siblings(':first');
149 | }
150 | }
151 | // hide last visible container, show next one and set tipnav container position
152 | wrap.css('left', 0);
153 | container.hide();
154 | $el.show();
155 | css = this.get_container_position($el);
156 | wrap.css(css);
157 | });
158 | }
159 | };
160 | });
161 |
162 | jQTipnav.define('Container', class {
163 | constructor($action_el, $copy, options, wrap) {
164 | var default_options = {
165 | 'trigger_event': 'mouseenter',
166 | 'fade_time': 500
167 | },
168 | options = $.extend({}, default_options, options || {}),
169 | wrap = wrap || false // wrap element? boolean or jQuery object
170 | ;
171 | this.action = new jQTipnav.Action($action_el, options);
172 | this.container = jQTipnav.Container.build($copy, wrap);
173 |
174 | this.$wrap = this.container.$wrap;
175 |
176 | if(wrap === true) {
177 | this.action.bind_container(this.$wrap);
178 | } else if(!wrap) {
179 | this.action.bind_container(this.container.$el);
180 | }
181 | this.action.setup_container(this.container.$el, this.$wrap);
182 | }
183 |
184 | static build_simple($copy, multi) {
185 | // bulds only container
186 | var id = unique.generate(),
187 | $el = $('')
188 | ;
189 | $el.find('ul').append($copy.children().clone());
190 | $copy.attr('data-jqtipnav-main', id);
191 | if(!!multi) {
192 | let arrow_p = $('< '),
193 | arrow_n = $('> ')
194 | ;
195 | $el.find('ul').prepend(arrow_p).append(arrow_n);
196 | }
197 | return $el;
198 | }
199 |
200 | static build($copy, wrap=false) {
201 | // build a container element markup and return it
202 | // adds $copy contents and arrows if content should be wrapped (multiple matched)
203 | var $el, $wrap;
204 | if(wrap === true) {
205 | $wrap = $('
');
206 | $wrap.css('position', 'absolute');
207 | $el = jQTipnav.Container.build_simple($copy, wrap);
208 | $el.show().appendTo($wrap);
209 | $wrap.appendTo('body');
210 | $wrap.hide();
211 | }
212 | else {
213 | $el = jQTipnav.Container.build_simple($copy, wrap);
214 | if(!!wrap) {
215 | $wrap = wrap;
216 | $el.appendTo($wrap);
217 | $el.hide();
218 | }
219 | else {
220 | $el.css('position', 'absolute');
221 | $el.appendTo('body');
222 | $el.hide();
223 | }
224 | }
225 | return {
226 | $wrap: $wrap,
227 | $el: $el,
228 | id: $el.attr('data-jqtipnav')
229 | };
230 | }
231 | });
232 |
233 | $.fn.tipnav = function (options) {
234 | // plugin definition
235 | var matchedEl = this,
236 | action,
237 | multi = matchedEl.length > 1,
238 | actions = {
239 | 'clean': function () {
240 | // can be called on element - $jqtipnav.tipnav('clean')
241 | // or on whole document $(document).tipnav('clean')
242 | // removes all the mess plugin has done under matched element
243 | var id, wrap_el, el;
244 | if (matchedEl.length === 1 && matchedEl.prop('nodeName').toLowerCase() === 'body'
245 | || matchedEl.prop('nodeName') === '#document') {
246 | // if matched element is document or body
247 | $('body').find('[data-jqtipnav]').remove();
248 | $('body').find('.jqtipnav-wrap').remove();
249 | $('[data-jqtipnav-main]').show().removeAttr('data-jqtipnav-main');
250 | }
251 | else {
252 | matchedEl.each(function () {
253 | // else remove all jqtpnav mess for only matched elements
254 | id = parseInt($(this).attr('data-jqtipnav-main'));
255 | if (typeof id !== 'undefined') {
256 | el = $('body').find('[data-jqtipnav=' + id + ']');
257 | wrap_el = el.parent('.jqtipnav-wrap');
258 | el.remove();
259 | if(wrap_el.length !== 0 && wrap_el.children().length == 0) {
260 | wrap_el.remove();
261 | }
262 | }
263 | $(this).show();
264 | $(this).removeAttr('data-jqtipnav-main');
265 | });
266 | }
267 | }
268 | },
269 | args = Array.prototype.slice.call(arguments),
270 | opts,
271 | wrap
272 | ;
273 | if(typeof(args[0]) !== 'string') {
274 | opts = options;
275 | action = opts.action_element;
276 | if(multi) {
277 | wrap = true;
278 | matchedEl.each(function() {
279 | let container;
280 | container = new jQTipnav.Container(action, $(this), opts, wrap);
281 | if(wrap === true) wrap = container.$wrap;
282 | });
283 | } else {
284 | new jQTipnav.Container(action, $(this), opts);
285 | }
286 | return matchedEl.hide();
287 | } else {
288 | actions[args[0]]();
289 | }
290 | return matchedEl;
291 | };
292 | });
293 |
294 |
--------------------------------------------------------------------------------