diff --git a/README.md b/README.md index 2075734..b551fb4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # jquery-page-tour JQuery-powered tour library that displays information about the page the user is on. -This is a quick little helper to spotlight DOMs on the page. Inspiration comes from Google's Android and Web-app help screens. +This is a quick little helper to spotlight DOMs on the page. Inspiration comes from Google's Android and Web-app help +screens. ## What does it do? This library lets you highlight DOMs on the page, and explain what their purpose is to the user. @@ -79,12 +80,39 @@ The following options are exposed: - `prevClass` - [string default: 'btn btn-default'] - The class for the "previous" link. - `exitClass` - [string default: 'btn btn-danger'] - The class for the "exit" link. - `defaultIndex` - [integer default: '9999'] - The default index of items without a manually set index. - - -#### HTML Attributes - -In the HTML, for each DOM you want to add to the tour, you must add at least one attribute to the -DOM: + - `styleOverwrites` - [object default: { \ }] - Provides a way to overwrite the tour styles. Keys and Defaults: + - `overlay` - `height: 100vh; width: 100%;` + - `tour` - `position: absolute;` + - `title` - `top: 0; left: 0; text-shadow: 0 0 10px #aaa; color: white; font-size: 2em; position: absolute; + font-weight: bold !important; width: 20rem;` + - `description` - `top: 0; left: 0; box-shadow: 0 0 7px #666; border-radius: 7px; padding: 11px; color: white; + font-size: 20px; position: absolute; width: 35rem; font-weight: normal !important;` + - `controls` - `top: 0; left: 0; margin: 8px 0; text-align: right; padding: 11px; color: white; font-size: 20px; + position: absolute; z-index: 10000; transition: all 0.75s; transition-timing-function: ease-in-out;` + - `control_buttons` - `margin-right: 10px; box-shadow: 0 0 3px white;` + - `targets` - `top: 0; left: 0; border-radius: 50%; border: 1px solid #333; position: absolute; opacity: 0.3;` + - `target_large` - `border-radius: 50%; position: relative; height: 80%; width: 80%; top: 9%; padding: 0; margin: 0 + auto;` + - `target_medium` - `border: 2px solid #666;` + - `target_small` - `border: 2px solid #aaa;` + - `transitions_main` - `transition: all 0.75s; transition-timing-function: ease-in-out;` + - `transition_box` - `transition-property: top, left; transition-duration: 0.75s; transition-timing-function: + ease-in-out;` + - `transition_key_frame` - `from { box-shadow: 0 0 1px rgba(64, 64, 64, 0.78); } to { box-shadow: 0 0 8px white; }` + - `target_glow` - `animation-duration: 1s; animation-name: targetGlow; animation-iteration-count: infinite; + animation-direction: alternate;` + - `target_glow` - `animation-duration: 1s; animation-name: targetGlow; animation-iteration-count: infinite; + animation-direction: alternate;` + - `target_delay_1` - `animation-delay: 0.3s;` + - `target_delay_2` - `overflow: hidden; transition: all 0.75s; transition-timing-function: ease-in-out; position: + absolute; border-radius: 5px;` + - `spotlight` - `overflow: hidden; transition: all 0.75s; transition-timing-function: ease-in-out; position: + absolute; border-radius: 5px;` + +#### Pre-Defined Page Tour HTML Attributes +If you're not controlling the tour 100% programmatically, you can pre-define HTML DOM objects on the page for the tour. + +In the HTML, each DOM you want to add to the pre-defined page tour must have at least one of the following attributes: - `data--title`: This attribute should be assigned the title of the DOM object tour stop. - example: `
` - `data--description`: This attribute should be assigned the description of what the tour item is. @@ -94,7 +122,7 @@ DOM: Both of these attributes can (and really should) be used together. However, only one is necessary. -#### Ordering Tour Stops +##### Ordering Tour Stops To enforce an order of which tour items are stopped at, you can add the `data--index` attribute: - example: `
` @@ -107,23 +135,42 @@ Items without an index are actually defaulted to an index of `9999`, though that #### Programmatically Controlling the Tour -The tour assigns a window-scoped dict object as `PageTour` at initialization. The `PageTour` object has 4 publicly +The tour assigns a window-scoped dict object as `PageTour` at initialization. The `PageTour` object has publicly accessible methods: + - `.switchTo(selector[, options])` - Programmatically sets the tour focus, title, and description. + - `selector` [jQuery Object | String] : Use a jQuery object or selector path string of DOM object to focus tour on. + - `options` [optional Object] - Options for the switchTo method execution: + - `title` [optional String] : The title of the object. + - `description` [optional String] : The description of the object. + - `next` [optional Function] : A programmatic function to fire when the **Next** button is clicked. (Hidden if omitted) + - `prev` [optional Function] : A programmatic function to fire when the **Previous** button is clicked. (Hidden if omitted) - `.open()` - Opens the tour. If the tour has previously been opened, it will open from where it was left off. - `.next()` - Moves the tour on to the next tour item. - `.prev()` - Moves the tour back to the previous tour item. - - `.exit()` - Closes the tour. - - `.rediscover()` - [deprecated] Rediscover any new DOMs on the page. This is not really necessary at all. Each time the tour is + - `.exit()` - Closes the tour. opened, the DOMs are rediscovered. -The only method completely necessary for tour operation is the `.open()` method. This starts the tour. -However, controls to proceed through the tour and exit the tour are displayed in the tour. +The only methods completely necessary for tour operation is the `.open()` method **or** `.switchTo()` method. These +methods start the tour. However, controls to proceed through the tour and exit the tour are displayed in the tour. -Example: +HTML Pre-configured Example: ```JavaScript // Instantiate the tour: var PageTour = $.fn.PageTour(); // Open the tour: PageTour.open(); +``` +HTML Programmatic Example: +```JavaScript +// Instantiate the tour: +var PageTour = $.fn.PageTour(); + +// Open the tour to a specific DOM object: +PageTour.switchTo('#some_DOM', { + title: 'Some DOM Object', + description: 'This is a DOM object on the page.', + next: switchTo('#another_DOM', {...}), + next: switchTo('#a_previous_DOM', {...}) +}); ``` \ No newline at end of file diff --git a/index.html b/index.html index ed9d100..ad51fef 100644 --- a/index.html +++ b/index.html @@ -41,7 +41,7 @@

Paris

Boomer! -
+

Tokyo

Tokyo is the capital of Japan.

It is the center of the Greater Tokyo Area, and the most populous metropolitan area in the world.

diff --git a/js/tour.js b/js/tour.js index b508e21..7fa9e93 100644 --- a/js/tour.js +++ b/js/tour.js @@ -11,8 +11,12 @@ $.fn.PageTour = function (opts) { nextClass : 'btn btn-primary', prevClass : 'btn btn-default', exitClass : 'btn btn-danger', - defaultIndex : 9999 + defaultIndex : 9999, + styleOverwrites : {} }, opts), + programmatic = false, + programmaticNext = function () {}, + programmaticPrev = function () {}, dims = {}, doms = [], elId = 0, @@ -113,7 +117,7 @@ $.fn.PageTour = function (opts) { elements.body.append(elements.style); return { - rediscover : discoverDoms, + switchTo : switchTo, next : next, prev : prev, open : open, @@ -132,7 +136,37 @@ $.fn.PageTour = function (opts) { } } + function switchTo(selector, options) { + if (! options) options = {}; + programmatic = true; + updateTourTitle(options.title || ''); + updateTourDesc(options.description || ''); + + if (options.next) { + programmaticNext = options.next; + elements.nextBtn.show(); + } else { + elements.nextBtn.hide(); + } + if (options.prev) { + elements.prevBtn.show(); + programmaticPrev = options.prev; + } else { + elements.prevBtn.hide(); + } + + elements.tour.show(); + + var dom = typeof selector === 'string' ? $(selector) : selector; + + doTourCalculations(dom); + shadowElement(); + flowTargets(); + flowTour(dom) + } + function run() { + programmatic = false; updateTourTitle(doms[ elId ].attr('data-' + _o.prefix + '-title') || ''); updateTourDesc(doms[ elId ].attr('data-' + _o.prefix + '-description') || ''); doTourCalculations(); @@ -142,13 +176,21 @@ $.fn.PageTour = function (opts) { } function next() { - elId = elId + 1 === doms.length ? 0 : elId + 1; - run(); + if (! programmatic) { + elId = elId + 1 === doms.length ? 0 : elId + 1; + run(); + } else if (typeof programmaticNext === 'function') { + programmaticNext(); + } } function prev() { - elId = elId === 0 ? doms.length - 1 : elId - 1; - run(); + if (! programmatic) { + elId = elId === 0 ? doms.length - 1 : elId - 1; + run(); + } else if (typeof programmaticPrev === 'function') { + programmaticPrev(); + } } function exit() { @@ -211,7 +253,10 @@ $.fn.PageTour = function (opts) { } - function flowTour() { + function flowTour(dom) { + if (! dom) + dom = doms[ elId ] + elements.title.css({ 'left' : dims[ 'title_x' ], 'top' : dims[ 'title_y' ] @@ -228,7 +273,7 @@ $.fn.PageTour = function (opts) { }); var - offset = doms[ elId ].offset().top - 120, + offset = dom.offset().top - 120, title = dims[ 'title_y' ] - 10; $('html, body').animate({ @@ -267,7 +312,11 @@ $.fn.PageTour = function (opts) { * Calculates out all the X/Y locations for where to put the title and the description/controls * base on the coordinates and dimensions of the show element and the tour elements. */ - function doTourCalculations() { + function doTourCalculations(element) { + if (! element) { + element = doms[ elId ]; + } + // Get all the height/width and X/Y coordinates of all the elements involved with the tour // including the window viewport dimensions. var @@ -280,10 +329,10 @@ $.fn.PageTour = function (opts) { dims[ 'descHeight' ] = descHeight; dims[ 'winWidth' ] = parseInt($(window).outerWidth()); dims[ 'winHeight' ] = parseInt($(window).outerHeight()); - dims[ 'elWidth' ] = parseInt(doms[ elId ].outerWidth()); - dims[ 'elHeight' ] = parseInt(doms[ elId ].outerHeight()); - dims[ 'elLeft' ] = doms[ elId ].offset().left; - dims[ 'elTop' ] = doms[ elId ].offset().top; + dims[ 'elWidth' ] = parseInt(element.outerWidth()); + dims[ 'elHeight' ] = parseInt(element.outerHeight()); + dims[ 'elLeft' ] = element.offset().left; + dims[ 'elTop' ] = element.offset().top; dims[ 'elCenterX' ] = dims[ 'elLeft' ] + (dims[ 'elWidth' ] / 2); dims[ 'elCenterY' ] = dims[ 'elTop' ] + (dims[ 'elHeight' ] / 2); dims[ 'topSpace' ] = dims[ 'elTop' ]; @@ -330,19 +379,34 @@ $.fn.PageTour = function (opts) { // Get the X/Y coordinates for the description/controls if (dims[ 'leftSpace' ] >= (descWidth + (_o.horizontalPadding * 2))) { // Check the left side space dims[ 'desc_x' ] = dims[ 'elLeft' ] - descWidth - (_o.horizontalPadding * 2); - dims[ 'desc_y' ] = dims[ 'title_y' ] + (_o.verticalPadding * 2) + ((titleIs === 'left') ? titleHeight + _o.verticalPadding : 0); + dims[ 'desc_y' ] = dims[ 'title_y' ] + (_o.verticalPadding * 2) + ((titleIs === 'left') + ? titleHeight + _o.verticalPadding + : 0 + ); } else if (dims[ 'rightSpace' ] >= (descWidth + (_o.horizontalPadding * 2))) { // Check the right side space dims[ 'desc_x' ] = dims[ 'elLeft' ] + dims[ 'elWidth' ] + _o.horizontalPadding; - dims[ 'desc_y' ] = dims[ 'title_y' ] + (_o.verticalPadding * 2) + ((titleIs === 'right') ? titleHeight + _o.verticalPadding : 0); - } else if (dims[ 'topSpace' ] >= (descHeight + (_o.verticalPadding * 4) + ((titleIs === 'top') ? titleHeight : 0)) && titleIs === 'top') { // check for space above the element - dims[ 'desc_y' ] = dims[ 'elTop' ] - _o.verticalPadding - descHeight - ((titleIs === 'top') ? titleHeight + _o.verticalPadding : 0); + dims[ 'desc_y' ] = dims[ 'title_y' ] + (_o.verticalPadding * 2) + ((titleIs === 'right') + ? titleHeight + _o.verticalPadding + : 0 + ); + } else if (dims[ 'topSpace' ] >= (descHeight + (_o.verticalPadding * 4) + ((titleIs === 'top') + ? titleHeight + : 0) + ) && titleIs === 'top') { // check for space above the element + dims[ 'desc_y' ] = dims[ 'elTop' ] - _o.verticalPadding - descHeight - ((titleIs === 'top') + ? titleHeight + _o.verticalPadding + : 0 + ); dims[ 'desc_x' ] = (dims[ 'winWidth' ] / 2) - titleWidth; if (titleIs === 'top') { dims[ 'title_y' ] -= descHeight; } } else { // Must go on the bottom. dims[ 'desc_x' ] = (dims[ 'winWidth' ] / 2) - descWidth; - dims[ 'desc_y' ] = dims[ 'elTop' ] + dims[ 'elHeight' ] + (_o.verticalPadding * 2) + ((titleIs === 'bottom') ? titleHeight + _o.verticalPadding : 0); + dims[ 'desc_y' ] = dims[ 'elTop' ] + dims[ 'elHeight' ] + (_o.verticalPadding * 2) + ((titleIs === 'bottom') + ? titleHeight + _o.verticalPadding + : 0 + ); if (titleIs === 'right') { dims[ 'desc_x' ] = dims[ 'title_x' ] - (descWidth * 0.25); dims[ 'desc_y' ] = dims[ 'desc_y' ] + _o.verticalPadding; @@ -351,25 +415,93 @@ $.fn.PageTour = function (opts) { } function setupStyle() { - elements.style.html( - '#' + _o.prefix + '_tour, #' + _o.prefix + '_overlay { height: 100vh; width: 100%; }\n' + - '#' + _o.prefix + '_tour { position: absolute; }\n' + - '.' + _o.prefix + '_title { top: 0; left: 0; text-shadow: 0 0 10px #aaa; color: white; font-size: 2em; position: absolute; font-weight: bold !important; width: 20rem; }\n' + - '.' + _o.prefix + '_description { top: 0; left: 0; box-shadow: 0 0 7px #666; border-radius: 7px; padding: 11px; color: white; font-size: 20px; position: absolute; width: 35rem; font-weight: normal !important; }\n' + - '.' + _o.prefix + '_controls { top: 0; left: 0; margin: 8px 0; text-align: right; padding: 11px; color: white; font-size: 20px; position: absolute; z-index: 10000; transition: all 0.75s; transition-timing-function: ease-in-out; }\n' + - '.' + _o.prefix + '_controls .btn { margin-right: 10px; box-shadow: 0 0 3px white; }\n' + - '#' + _o.prefix + '_targets { top: 0; left: 0; border-radius: 50%; border: 1px solid #333; position: absolute; opacity: 0.3; }\n' + - '.' + _o.prefix + '_target { border-radius: 50%; position: relative; height: 80%; width: 80%; top: 9%; padding: 0; margin: 0 auto; }\n' + - '.' + _o.prefix + '_target_small { border: 2px solid #aaa; }\n' + - '.' + _o.prefix + '_target_medium { border: 2px solid #666; }\n' + - '.' + _o.prefix + '_element_transition { transition: all 0.75s; transition-timing-function: ease-in-out; }\n' + - '.' + _o.prefix + '_box_transition { transition-property: top, left; transition-duration: 0.75s; transition-timing-function: ease-in-out; }\n' + - '@keyframes targetGlow { from { box-shadow: 0 0 1px rgba(64, 64, 64, 0.78); } to { box-shadow: 0 0 8px white; } }\n' + - '.' + _o.prefix + '_glow { animation-duration: 1s; animation-name: targetGlow; animation-iteration-count: infinite; animation-direction: alternate; }\n' + - '.' + _o.prefix + '_animation_delay1 { animation-delay: 0.3s; }\n' + - '.' + _o.prefix + '_animation_delay2 { animation-delay: 0.6s; }\n' + - '.' + _o.prefix + '_shade { overflow: hidden; transition: all 0.75s; transition-timing-function: ease-in-out; position: absolute; border-radius: 5px; }' - ); + var styleSheet = ''; + var styles = { + 'overlay' : { + selector : '#' + _o.prefix + '_tour, #' + _o.prefix + '_overlay', + style : 'height: 100vh; width: 100%;' + }, + 'tour' : { + selector : '#' + _o.prefix + '_tour', + style : 'position: absolute;' + }, + 'title' : { + selector : '.' + _o.prefix + '_title', + style : 'top: 0; left: 0; text-shadow: 0 0 10px #aaa; color: white; font-size: 2em; position: absolute; font-weight: bold !important; width: 20rem;' + }, + 'description' : { + selector : '.' + _o.prefix + '_description', + style : 'top: 0; left: 0; box-shadow: 0 0 7px #666; border-radius: 7px; padding: 11px; color: white; font-size: 20px; position: absolute; width: 35rem; font-weight: normal !important;' + }, + 'controls' : { + selector : '.' + _o.prefix + '_controls', + style : 'top: 0; left: 0; margin: 8px 0; text-align: right; padding: 11px; color: white; font-size: 20px; position: absolute; z-index: 10000; transition: all 0.75s; transition-timing-function: ease-in-out;' + }, + 'control_buttons' : { + selector : '.' + _o.prefix + '_controls .btn', + style : 'margin-right: 10px; box-shadow: 0 0 3px white;' + }, + 'targets' : { + selector : '#' + _o.prefix + '_targets', + style : 'top: 0; left: 0; border-radius: 50%; border: 1px solid #333; position: absolute; opacity: 0.3;' + }, + 'target_large' : { + selector : '.' + _o.prefix + '_target', + style : 'border-radius: 50%; position: relative; height: 80%; width: 80%; top: 9%; padding: 0; margin: 0 auto;' + }, + 'target_small' : { + selector : '.' + _o.prefix + '_target_small', + style : 'border: 2px solid #aaa;' + }, + 'target_medium' : { + selector : '.' + _o.prefix + '_target_medium', + style : 'border: 2px solid #666;' + }, + 'transitions_main' : { + selector : '.' + _o.prefix + '_element_transition', + style : 'transition: all 0.75s; transition-timing-function: ease-in-out;' + }, + 'transition_box' : { + selector : '.' + _o.prefix + '_box_transition', + style : 'transition-property: top, left; transition-duration: 0.75s; transition-timing-function: ease-in-out;' + }, + 'transition_key_frame' : { + selector : '@keyframes targetGlow', + style : 'from { box-shadow: 0 0 1px rgba(64, 64, 64, 0.78); } to { box-shadow: 0 0 8px white; }' + }, + 'target_glow' : { + selector : '.' + _o.prefix + '_glow', + style : 'animation-duration: 1s; animation-name: targetGlow; animation-iteration-count: infinite; animation-direction: alternate;' + }, + 'target_delay_1' : { + selector : '.' + _o.prefix + '_animation_delay1', + style : 'animation-delay: 0.3s;' + }, + 'target_delay_2' : { + selector : '.' + _o.prefix + '_animation_delay2', + style : 'animation-delay: 0.6s;' + }, + 'spotlight' : { + selector : '.' + _o.prefix + '_shade', + style : 'overflow: hidden; transition: all 0.75s; transition-timing-function: ease-in-out; position: absolute; border-radius: 5px;' + } + }; + + for (var style in styles) { + if (! styles.hasOwnProperty(style)) return; + + styleSheet += styles[ style ].selector + ' { '; + + if (_o.styleOverwrites.hasOwnProperty(style)) { + styleSheet += _o.styleOverwrites[ style ]; + } else { + styleSheet += styles[ style ].style; + } + + styleSheet += ' }\n'; + } + + elements.style.html(styleSheet); } function textWidth() {