camaleon_cms 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of camaleon_cms might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ac6264aa837e1bf65d11ef13a9ddee7d7d287a7c
4
- data.tar.gz: 030b3f0ba1bc3720d79aa2b99923bb812f019f95
3
+ metadata.gz: dde50d023af5d5b8c20010a1b05d865b2a9102fe
4
+ data.tar.gz: d17b74a4a5bac6fb7ef869784a778f27dd29a0a8
5
5
  SHA512:
6
- metadata.gz: 14c3f2a6edc55438f0788693fa764c817a7b26bd899774ace020d402ab7208561380629b17a29720e2483368b7955252d98bd5379b0ce029c5f2a01eb63b39a4
7
- data.tar.gz: b1939cf9c2dc983e74ce60eff0580a232aa534da332cca90addba07b9899ffb0db33a4089ff97be49f96915930f356ff21c168518f8c8b747bc39e0a98225d3e
6
+ metadata.gz: 035738fecbcab32bafe7f3591c6d428e8ea6264152b330c8d56a2e3d5064ba026e65d94b99b6957b7f1394b592f2ee03f35f5b34c22208719e2bafae20bbc1c5
7
+ data.tar.gz: 4a9126ebdd52874f2fcf3c7486ed9c12473b175ed3e5fd672f179a3489ddb69d4243d8b6d562c9163c8149b6d3d855ac4e85ad9a94c0774f1ce9825ffac170ed
data/README.md CHANGED
@@ -179,94 +179,4 @@ http://camaleon.tuzitio.com/license.html/
179
179
  Visit the web site for more information: http://camaleon.tuzitio.com/
180
180
 
181
181
  ## Version History
182
- ### 1.0
183
- * new template for admin panel
184
- * gem plugin generator
185
- console:
186
- rails g camaleon_cms:gem_plugin post_reorder
187
- bundle install
188
- visit frontend: http://localhost:3000/plugins/post_reorder/index
189
- visit backend: http://localhost:3000/admin/plugins/post_reorder/index
190
- Check here to publish your gem http://guides.rubygems.org/publishing/
191
- * gem plugin support added
192
- * changed post structure to improve sql query
193
- * added custom field orderer
194
- sort_by_field(...)
195
- sample: Site.first.posts.sort_by_field("untitled-field-attributes", "desc")
196
- * Added layouts selector like template selector
197
- * Added support custom fields for menu items
198
- Add any custom fields into menus and it will appear for each menu item (ideal to add icons or custom text for each menu)
199
- * I18n(..) for javascript
200
- You don't need to print your translations in html, put your translations in:
201
- Sample:
202
- ``en:
203
- admin:
204
- js:
205
- button
206
- my_text: "asasa"
207
-
208
- I18n("button.my_text")``
209
- * hooks for email
210
- * shortcodes support for content editor
211
- * hook "user_update_more_actions" to add more action in user profile
212
- * added slug render support for categories, post_types, post_tags. Check doc.
213
- * fixed the default view "post.html.erb" into "single.html.erb". Check doc.
214
- * added the_field!(..) and get_field!(..) method in custom fields to manage empty values. Check doc.
215
- * added a control for logged users the_edit_link
216
- * added the_author method for posts
217
- * added the_categories method for sites
218
- * added cama_edit_link(...) to create common edit links anywhere, this verify if current visitor was logged in
219
- * added default shortcodes: load_libreries (load custom libraries), asset (render an url or image tag with the url), post_url (render an url for a post by id or slug)
220
- * fixed shortcode to support shortcodes with almost the same name. sample: [asset] [asset_path]
221
- * added method cama_strip_shortcodes(..) to strip all shortcodes
222
- * added get_fields(..) to get multiple values of a custom field
223
- * added filters for categories (.no_empty, .empty)
224
- * Added method for post: set_summary(..)
225
- * Added method for post: set_thumb(..)
226
- * Added method for post: increment_visits!
227
- * Added method for post: total_visits
228
- * Added method for post: total_comments
229
- * Added extra support values for method add_post(..) in post_type object
230
- * Added shortcode "post_url"
231
- Permit to generate the url of a post (add path='' to generate the path and not the full url,
232
- add id='123' to use the POST ID,
233
- add key='my_slug' to use the POST SLUG,
234
- add link='true' to generate the full link,
235
- add title='my title' text of the link (default post title),
236
- add target='_blank' to open the link in a new window this is valid only if link is present),
237
- sample: [post_url id='122' link=true target='_blank']
238
- * Added shortcode "asset"
239
- Permit to generate an asset url (
240
- add file='' asset file path,
241
- add as_path='true' to generate only the path and not the full url,
242
- add class='my_class' to setup image class,
243
- add style='height: 100px; width: 200px;...' to setup image style,
244
- add image='true' to generate the image tag with this url),
245
- sample: <img src=\"[asset as_path='true' file='themes/my_theme/assets/img/signature.png']\" /> or [asset image='true' file='themes/my_theme/assets/img/signature.png' style='height: 50px;']
246
- * Added shortcode "custom_field"
247
- Permit you to include your custom fields in anywhere.
248
- key: slug or key of the custom_field
249
- attrs: custom html attributes
250
- render: (true) enable to render the custom field as html. (Sample text_field: <span>my_field_value</span>)
251
- post_slug: (Optional, default current post) slug or key of a Post.
252
- Sample1: [custom_field key='subtitle']
253
- Sample2: [custom_field key='subtitle' post_slug='contact' render=true attrs='style=\"width: 50px;\"'] // return the custom field of page with slug = contact
254
-
255
- ### 0.2.1
256
- * fixed sprockets problem: https://github.com/owen2345/camaleon-cms/issues/53
257
-
258
- ### 0.2.0
259
- * datetimepicker
260
- * Plugin files separated in two files, please update with: rails g camaleon_cms:install //and replace plugin_routes.rb
261
- * Added the edit url for post/posttypes/categories
262
- * Added plugin upgrade support
263
- * Added confirm for disable/enable a plugin
264
-
265
- ### 0.1.10
266
- * Fix rufus initializer
267
- * Changed plugins documentation link
268
- * Fixed current locale for editors
269
- * Rails 4.1 support added
270
-
271
- ### 0.1.6
272
- * Added Italian language support
182
+ http://camaleon.tuzitio.com/version-history.html
@@ -74,7 +74,7 @@ module Plugins::ContactForm::ContactFormHelper
74
74
  end
75
75
 
76
76
  def contact_form_admin_before_load
77
- admin_menu_append_menu_item("settings", {icon: "envelope-o", title: t('plugin.contact_form.contact_form'), url: admin_plugins_contact_form_admin_forms_path})
77
+ admin_menu_append_menu_item("settings", {icon: "envelope-o", title: t('plugin.contact_form.contact_form'), url: admin_plugins_contact_form_admin_forms_path, datas: "data-intro='This plugin permit you to create you contact forms with desired fields and paste your short_code in any content.' data-position='right'"})
78
78
  end
79
79
 
80
80
  def contact_form_app_before_load
@@ -2,8 +2,25 @@ jQuery(function($){
2
2
  // initialize all validations for forms
3
3
  init_form_validations();
4
4
  setTimeout(page_actions, 1000);
5
+ setTimeout(init_intro, 500);
5
6
  });
6
7
 
8
+ // show admin intro presentation
9
+ function init_intro(){
10
+ if($("body").attr("data-intro")) return;
11
+ var finish = function(){
12
+ $.get(root_url+"/admin/ajax", {mode: "save_intro"});
13
+ }
14
+ introJs().setOptions({exitOnEsc: false,
15
+ exitOnOverlayClick: false,
16
+ showStepNumbers: false,
17
+ showBullets: false,
18
+ disableInteraction: true
19
+ }).oncomplete(finish).onexit(finish).onbeforechange(function(ele) {
20
+ if($(ele).hasClass("treeview") && !$(ele).hasClass("active")) $(ele).children("a").click();
21
+ }).start();
22
+ }
23
+
7
24
  // basic and common actions
8
25
  var page_actions = function(){
9
26
  // button actions
@@ -40,3 +40,5 @@
40
40
 
41
41
  //= require ./lte/app
42
42
 
43
+ //= require ./introjs/intro.min
44
+
@@ -0,0 +1,1316 @@
1
+ /**
2
+ * Intro.js v1.1.1
3
+ * https://github.com/usablica/intro.js
4
+ * MIT licensed
5
+ *
6
+ * Copyright (C) 2013 usabli.ca - A weekend project by Afshin Mehrabani (@afshinmeh)
7
+ */
8
+
9
+ (function (root, factory) {
10
+ if (typeof exports === 'object') {
11
+ // CommonJS
12
+ factory(exports);
13
+ } else if (typeof define === 'function' && define.amd) {
14
+ // AMD. Register as an anonymous module.
15
+ define(['exports'], factory);
16
+ } else {
17
+ // Browser globals
18
+ factory(root);
19
+ }
20
+ } (this, function (exports) {
21
+ //Default config/variables
22
+ var VERSION = '1.1.1';
23
+
24
+ /**
25
+ * IntroJs main class
26
+ *
27
+ * @class IntroJs
28
+ */
29
+ function IntroJs(obj) {
30
+ this._targetElement = obj;
31
+
32
+ this._options = {
33
+ /* Next button label in tooltip box */
34
+ nextLabel: 'Next &rarr;',
35
+ /* Previous button label in tooltip box */
36
+ prevLabel: '&larr; Back',
37
+ /* Skip button label in tooltip box */
38
+ skipLabel: 'Skip',
39
+ /* Done button label in tooltip box */
40
+ doneLabel: 'Done',
41
+ /* Default tooltip box position */
42
+ tooltipPosition: 'bottom',
43
+ /* Next CSS class for tooltip boxes */
44
+ tooltipClass: '',
45
+ /* CSS class that is added to the helperLayer */
46
+ highlightClass: '',
47
+ /* Close introduction when pressing Escape button? */
48
+ exitOnEsc: true,
49
+ /* Close introduction when clicking on overlay layer? */
50
+ exitOnOverlayClick: true,
51
+ /* Show step numbers in introduction? */
52
+ showStepNumbers: true,
53
+ /* Let user use keyboard to navigate the tour? */
54
+ keyboardNavigation: true,
55
+ /* Show tour control buttons? */
56
+ showButtons: true,
57
+ /* Show tour bullets? */
58
+ showBullets: true,
59
+ /* Show tour progress? */
60
+ showProgress: false,
61
+ /* Scroll to highlighted element? */
62
+ scrollToElement: true,
63
+ /* Set the overlay opacity */
64
+ overlayOpacity: 0.8,
65
+ /* Precedence of positions, when auto is enabled */
66
+ positionPrecedence: ["bottom", "top", "right", "left"],
67
+ /* Disable an interaction with element? */
68
+ disableInteraction: false
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Initiate a new introduction/guide from an element in the page
74
+ *
75
+ * @api private
76
+ * @method _introForElement
77
+ * @param {Object} targetElm
78
+ * @returns {Boolean} Success or not?
79
+ */
80
+ function _introForElement(targetElm) {
81
+ var introItems = [],
82
+ self = this;
83
+
84
+ if (this._options.steps) {
85
+ //use steps passed programmatically
86
+ for (var i = 0, stepsLength = this._options.steps.length; i < stepsLength; i++) {
87
+ var currentItem = _cloneObject(this._options.steps[i]);
88
+ //set the step
89
+ currentItem.step = introItems.length + 1;
90
+ //use querySelector function only when developer used CSS selector
91
+ if (typeof(currentItem.element) === 'string') {
92
+ //grab the element with given selector from the page
93
+ currentItem.element = document.querySelector(currentItem.element);
94
+ }
95
+
96
+ //intro without element
97
+ if (typeof(currentItem.element) === 'undefined' || currentItem.element == null) {
98
+ var floatingElementQuery = document.querySelector(".introjsFloatingElement");
99
+
100
+ if (floatingElementQuery == null) {
101
+ floatingElementQuery = document.createElement('div');
102
+ floatingElementQuery.className = 'introjsFloatingElement';
103
+
104
+ document.body.appendChild(floatingElementQuery);
105
+ }
106
+
107
+ currentItem.element = floatingElementQuery;
108
+ currentItem.position = 'floating';
109
+ }
110
+
111
+ if (currentItem.element != null) {
112
+ introItems.push(currentItem);
113
+ }
114
+ }
115
+
116
+ } else {
117
+ //use steps from data-* annotations
118
+ var allIntroSteps = targetElm.querySelectorAll('*[data-intro]');
119
+ //if there's no element to intro
120
+ if (allIntroSteps.length < 1) {
121
+ return false;
122
+ }
123
+
124
+ //first add intro items with data-step
125
+ for (var i = 0, elmsLength = allIntroSteps.length; i < elmsLength; i++) {
126
+ var currentElement = allIntroSteps[i];
127
+ var step = parseInt(currentElement.getAttribute('data-step'), 10);
128
+
129
+ if (step > 0) {
130
+ introItems[step - 1] = {
131
+ element: currentElement,
132
+ intro: currentElement.getAttribute('data-intro'),
133
+ step: parseInt(currentElement.getAttribute('data-step'), 10),
134
+ tooltipClass: currentElement.getAttribute('data-tooltipClass'),
135
+ highlightClass: currentElement.getAttribute('data-highlightClass'),
136
+ position: currentElement.getAttribute('data-position') || this._options.tooltipPosition
137
+ };
138
+ }
139
+ }
140
+
141
+ //next add intro items without data-step
142
+ //todo: we need a cleanup here, two loops are redundant
143
+ var nextStep = 0;
144
+ for (var i = 0, elmsLength = allIntroSteps.length; i < elmsLength; i++) {
145
+ var currentElement = allIntroSteps[i];
146
+
147
+ if (currentElement.getAttribute('data-step') == null) {
148
+
149
+ while (true) {
150
+ if (typeof introItems[nextStep] == 'undefined') {
151
+ break;
152
+ } else {
153
+ nextStep++;
154
+ }
155
+ }
156
+
157
+ introItems[nextStep] = {
158
+ element: currentElement,
159
+ intro: currentElement.getAttribute('data-intro'),
160
+ step: nextStep + 1,
161
+ tooltipClass: currentElement.getAttribute('data-tooltipClass'),
162
+ highlightClass: currentElement.getAttribute('data-highlightClass'),
163
+ position: currentElement.getAttribute('data-position') || this._options.tooltipPosition
164
+ };
165
+ }
166
+ }
167
+ }
168
+
169
+ //removing undefined/null elements
170
+ var tempIntroItems = [];
171
+ for (var z = 0; z < introItems.length; z++) {
172
+ introItems[z] && tempIntroItems.push(introItems[z]); // copy non-empty values to the end of the array
173
+ }
174
+
175
+ introItems = tempIntroItems;
176
+
177
+ //Ok, sort all items with given steps
178
+ introItems.sort(function (a, b) {
179
+ return a.step - b.step;
180
+ });
181
+
182
+ //set it to the introJs object
183
+ self._introItems = introItems;
184
+
185
+ //add overlay layer to the page
186
+ if(_addOverlayLayer.call(self, targetElm)) {
187
+ //then, start the show
188
+ _nextStep.call(self);
189
+
190
+ var skipButton = targetElm.querySelector('.introjs-skipbutton'),
191
+ nextStepButton = targetElm.querySelector('.introjs-nextbutton');
192
+
193
+ self._onKeyDown = function(e) {
194
+ if (e.keyCode === 27 && self._options.exitOnEsc == true) {
195
+ //escape key pressed, exit the intro
196
+ //check if exit callback is defined
197
+ if (self._introExitCallback != undefined) {
198
+ self._introExitCallback.call(self);
199
+ }
200
+ _exitIntro.call(self, targetElm);
201
+ } else if(e.keyCode === 37) {
202
+ //left arrow
203
+ _previousStep.call(self);
204
+ } else if (e.keyCode === 39) {
205
+ //right arrow
206
+ _nextStep.call(self);
207
+ } else if (e.keyCode === 13) {
208
+ //srcElement === ie
209
+ var target = e.target || e.srcElement;
210
+ if (target && target.className.indexOf('introjs-prevbutton') > 0) {
211
+ //user hit enter while focusing on previous button
212
+ _previousStep.call(self);
213
+ } else if (target && target.className.indexOf('introjs-skipbutton') > 0) {
214
+ //user hit enter while focusing on skip button
215
+ if (self._introItems.length - 1 == self._currentStep && typeof (self._introCompleteCallback) === 'function') {
216
+ self._introCompleteCallback.call(self);
217
+ }
218
+ //check if any callback is defined
219
+ if (self._introExitCallback != undefined) {
220
+ self._introExitCallback.call(self);
221
+ }
222
+ _exitIntro.call(self, targetElm);
223
+ } else {
224
+ //default behavior for responding to enter
225
+ _nextStep.call(self);
226
+ }
227
+
228
+ //prevent default behaviour on hitting Enter, to prevent steps being skipped in some browsers
229
+ if(e.preventDefault) {
230
+ e.preventDefault();
231
+ } else {
232
+ e.returnValue = false;
233
+ }
234
+ }
235
+ };
236
+
237
+ self._onResize = function(e) {
238
+ _setHelperLayerPosition.call(self, document.querySelector('.introjs-helperLayer'));
239
+ _setHelperLayerPosition.call(self, document.querySelector('.introjs-tooltipReferenceLayer'));
240
+ };
241
+
242
+ if (window.addEventListener) {
243
+ if (this._options.keyboardNavigation) {
244
+ window.addEventListener('keydown', self._onKeyDown, true);
245
+ }
246
+ //for window resize
247
+ window.addEventListener('resize', self._onResize, true);
248
+ } else if (document.attachEvent) { //IE
249
+ if (this._options.keyboardNavigation) {
250
+ document.attachEvent('onkeydown', self._onKeyDown);
251
+ }
252
+ //for window resize
253
+ document.attachEvent('onresize', self._onResize);
254
+ }
255
+ }
256
+ return false;
257
+ }
258
+
259
+ /*
260
+ * makes a copy of the object
261
+ * @api private
262
+ * @method _cloneObject
263
+ */
264
+ function _cloneObject(object) {
265
+ if (object == null || typeof (object) != 'object' || typeof (object.nodeType) != 'undefined') {
266
+ return object;
267
+ }
268
+ var temp = {};
269
+ for (var key in object) {
270
+ if (typeof (jQuery) != 'undefined' && object[key] instanceof jQuery) {
271
+ temp[key] = object[key];
272
+ } else {
273
+ temp[key] = _cloneObject(object[key]);
274
+ }
275
+ }
276
+ return temp;
277
+ }
278
+ /**
279
+ * Go to specific step of introduction
280
+ *
281
+ * @api private
282
+ * @method _goToStep
283
+ */
284
+ function _goToStep(step) {
285
+ //because steps starts with zero
286
+ this._currentStep = step - 2;
287
+ if (typeof (this._introItems) !== 'undefined') {
288
+ _nextStep.call(this);
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Go to next step on intro
294
+ *
295
+ * @api private
296
+ * @method _nextStep
297
+ */
298
+ function _nextStep() {
299
+ this._direction = 'forward';
300
+
301
+ if (typeof (this._currentStep) === 'undefined') {
302
+ this._currentStep = 0;
303
+ } else {
304
+ ++this._currentStep;
305
+ }
306
+
307
+ if ((this._introItems.length) <= this._currentStep) {
308
+ //end of the intro
309
+ //check if any callback is defined
310
+ if (typeof (this._introCompleteCallback) === 'function') {
311
+ this._introCompleteCallback.call(this);
312
+ }
313
+ _exitIntro.call(this, this._targetElement);
314
+ return;
315
+ }
316
+
317
+ var nextStep = this._introItems[this._currentStep];
318
+ if (typeof (this._introBeforeChangeCallback) !== 'undefined') {
319
+ this._introBeforeChangeCallback.call(this, nextStep.element);
320
+ }
321
+
322
+ //_showElement.call(this, nextStep);
323
+ var w = $(nextStep.element).attr("data-wait");
324
+ var thiss = this;
325
+ var _f = function(){ _showElement.call(thiss, nextStep); };
326
+ if(w) setTimeout(_f, w); else _f();
327
+ }
328
+
329
+ /**
330
+ * Go to previous step on intro
331
+ *
332
+ * @api private
333
+ * @method _nextStep
334
+ */
335
+ function _previousStep() {
336
+ this._direction = 'backward';
337
+
338
+ if (this._currentStep === 0) {
339
+ return false;
340
+ }
341
+
342
+ var nextStep = this._introItems[--this._currentStep];
343
+ if (typeof (this._introBeforeChangeCallback) !== 'undefined') {
344
+ this._introBeforeChangeCallback.call(this, nextStep.element);
345
+ }
346
+
347
+ //_showElement.call(this, nextStep);
348
+ var w = $(nextStep.element).attr("data-wait");
349
+ var thiss = this;
350
+ var _f = function(){ _showElement.call(thiss, nextStep); };
351
+ if(w) setTimeout(_f, w); else _f();
352
+ }
353
+
354
+ /**
355
+ * Exit from intro
356
+ *
357
+ * @api private
358
+ * @method _exitIntro
359
+ * @param {Object} targetElement
360
+ */
361
+ function _exitIntro(targetElement) {
362
+ //remove overlay layer from the page
363
+ var overlayLayer = targetElement.querySelector('.introjs-overlay');
364
+
365
+ //return if intro already completed or skipped
366
+ if (overlayLayer == null) {
367
+ return;
368
+ }
369
+
370
+ //for fade-out animation
371
+ overlayLayer.style.opacity = 0;
372
+ setTimeout(function () {
373
+ if (overlayLayer.parentNode) {
374
+ overlayLayer.parentNode.removeChild(overlayLayer);
375
+ }
376
+ }, 500);
377
+
378
+ //remove all helper layers
379
+ var helperLayer = targetElement.querySelector('.introjs-helperLayer');
380
+ if (helperLayer) {
381
+ helperLayer.parentNode.removeChild(helperLayer);
382
+ }
383
+
384
+ var referenceLayer = targetElement.querySelector('.introjs-tooltipReferenceLayer');
385
+ if (referenceLayer) {
386
+ referenceLayer.parentNode.removeChild(referenceLayer);
387
+ }
388
+ //remove disableInteractionLayer
389
+ var disableInteractionLayer = targetElement.querySelector('.introjs-disableInteraction');
390
+ if (disableInteractionLayer) {
391
+ disableInteractionLayer.parentNode.removeChild(disableInteractionLayer);
392
+ }
393
+
394
+ //remove intro floating element
395
+ var floatingElement = document.querySelector('.introjsFloatingElement');
396
+ if (floatingElement) {
397
+ floatingElement.parentNode.removeChild(floatingElement);
398
+ }
399
+
400
+ //remove `introjs-showElement` class from the element
401
+ var showElement = document.querySelector('.introjs-showElement');
402
+ if (showElement) {
403
+ showElement.className = showElement.className.replace(/introjs-[a-zA-Z]+/g, '').replace(/^\s+|\s+$/g, ''); // This is a manual trim.
404
+ }
405
+
406
+ //remove `introjs-fixParent` class from the elements
407
+ var fixParents = document.querySelectorAll('.introjs-fixParent');
408
+ if (fixParents && fixParents.length > 0) {
409
+ for (var i = fixParents.length - 1; i >= 0; i--) {
410
+ fixParents[i].className = fixParents[i].className.replace(/introjs-fixParent/g, '').replace(/^\s+|\s+$/g, '');
411
+ }
412
+ }
413
+
414
+ //clean listeners
415
+ if (window.removeEventListener) {
416
+ window.removeEventListener('keydown', this._onKeyDown, true);
417
+ } else if (document.detachEvent) { //IE
418
+ document.detachEvent('onkeydown', this._onKeyDown);
419
+ }
420
+
421
+ //set the step to zero
422
+ this._currentStep = undefined;
423
+ }
424
+
425
+ /**
426
+ * Render tooltip box in the page
427
+ *
428
+ * @api private
429
+ * @method _placeTooltip
430
+ * @param {HTMLElement} targetElement
431
+ * @param {HTMLElement} tooltipLayer
432
+ * @param {HTMLElement} arrowLayer
433
+ * @param {HTMLElement} helperNumberLayer
434
+ */
435
+ function _placeTooltip(targetElement, tooltipLayer, arrowLayer, helperNumberLayer) {
436
+ var tooltipCssClass = '',
437
+ currentStepObj,
438
+ tooltipOffset,
439
+ targetOffset,
440
+ windowSize,
441
+ currentTooltipPosition;
442
+
443
+ //reset the old style
444
+ tooltipLayer.style.top = null;
445
+ tooltipLayer.style.right = null;
446
+ tooltipLayer.style.bottom = null;
447
+ tooltipLayer.style.left = null;
448
+ tooltipLayer.style.marginLeft = null;
449
+ tooltipLayer.style.marginTop = null;
450
+
451
+ arrowLayer.style.display = 'inherit';
452
+
453
+ if (typeof(helperNumberLayer) != 'undefined' && helperNumberLayer != null) {
454
+ helperNumberLayer.style.top = null;
455
+ helperNumberLayer.style.left = null;
456
+ }
457
+
458
+ //prevent error when `this._currentStep` is undefined
459
+ if (!this._introItems[this._currentStep]) return;
460
+
461
+ //if we have a custom css class for each step
462
+ currentStepObj = this._introItems[this._currentStep];
463
+ if (typeof (currentStepObj.tooltipClass) === 'string') {
464
+ tooltipCssClass = currentStepObj.tooltipClass;
465
+ } else {
466
+ tooltipCssClass = this._options.tooltipClass;
467
+ }
468
+
469
+ tooltipLayer.className = ('introjs-tooltip ' + tooltipCssClass).replace(/^\s+|\s+$/g, '');
470
+
471
+ currentTooltipPosition = this._introItems[this._currentStep].position;
472
+ if ((currentTooltipPosition == "auto" || this._options.tooltipPosition == "auto")) {
473
+ if (currentTooltipPosition != "floating") { // Floating is always valid, no point in calculating
474
+ currentTooltipPosition = _determineAutoPosition.call(this, targetElement, tooltipLayer, currentTooltipPosition);
475
+ }
476
+ }
477
+ targetOffset = _getOffset(targetElement);
478
+ tooltipOffset = _getOffset(tooltipLayer);
479
+ windowSize = _getWinSize();
480
+ switch (currentTooltipPosition) {
481
+ case 'top':
482
+ arrowLayer.className = 'introjs-arrow bottom';
483
+
484
+ var tooltipLayerStyleLeft = 15;
485
+ _checkRight(targetOffset, tooltipLayerStyleLeft, tooltipOffset, windowSize, tooltipLayer);
486
+ tooltipLayer.style.bottom = (targetOffset.height + 20) + 'px';
487
+ break;
488
+ case 'right':
489
+ tooltipLayer.style.left = (targetOffset.width + 20) + 'px';
490
+ if (targetOffset.top + tooltipOffset.height > windowSize.height) {
491
+ // In this case, right would have fallen below the bottom of the screen.
492
+ // Modify so that the bottom of the tooltip connects with the target
493
+ arrowLayer.className = "introjs-arrow left-bottom";
494
+ tooltipLayer.style.top = "-" + (tooltipOffset.height - targetOffset.height - 20) + "px";
495
+ } else {
496
+ arrowLayer.className = 'introjs-arrow left';
497
+ }
498
+ break;
499
+ case 'left':
500
+ if (this._options.showStepNumbers == true) {
501
+ tooltipLayer.style.top = '15px';
502
+ }
503
+
504
+ if (targetOffset.top + tooltipOffset.height > windowSize.height) {
505
+ // In this case, left would have fallen below the bottom of the screen.
506
+ // Modify so that the bottom of the tooltip connects with the target
507
+ tooltipLayer.style.top = "-" + (tooltipOffset.height - targetOffset.height - 20) + "px";
508
+ arrowLayer.className = 'introjs-arrow right-bottom';
509
+ } else {
510
+ arrowLayer.className = 'introjs-arrow right';
511
+ }
512
+ tooltipLayer.style.right = (targetOffset.width + 20) + 'px';
513
+
514
+ break;
515
+ case 'floating':
516
+ arrowLayer.style.display = 'none';
517
+
518
+ //we have to adjust the top and left of layer manually for intro items without element
519
+ tooltipLayer.style.left = '50%';
520
+ tooltipLayer.style.top = '50%';
521
+ tooltipLayer.style.marginLeft = '-' + (tooltipOffset.width / 2) + 'px';
522
+ tooltipLayer.style.marginTop = '-' + (tooltipOffset.height / 2) + 'px';
523
+
524
+ if (typeof(helperNumberLayer) != 'undefined' && helperNumberLayer != null) {
525
+ helperNumberLayer.style.left = '-' + ((tooltipOffset.width / 2) + 18) + 'px';
526
+ helperNumberLayer.style.top = '-' + ((tooltipOffset.height / 2) + 18) + 'px';
527
+ }
528
+
529
+ break;
530
+ case 'bottom-right-aligned':
531
+ arrowLayer.className = 'introjs-arrow top-right';
532
+
533
+ var tooltipLayerStyleRight = 0;
534
+ _checkLeft(targetOffset, tooltipLayerStyleRight, tooltipOffset, tooltipLayer);
535
+ tooltipLayer.style.top = (targetOffset.height + 20) + 'px';
536
+ break;
537
+
538
+ case 'bottom-middle-aligned':
539
+ arrowLayer.className = 'introjs-arrow top-middle';
540
+
541
+ var tooltipLayerStyleLeftRight = targetOffset.width / 2 - tooltipOffset.width / 2;
542
+ if (_checkLeft(targetOffset, tooltipLayerStyleLeftRight, tooltipOffset, tooltipLayer)) {
543
+ tooltipLayer.style.right = null;
544
+ _checkRight(targetOffset, tooltipLayerStyleLeftRight, tooltipOffset, windowSize, tooltipLayer);
545
+ }
546
+ tooltipLayer.style.top = (targetOffset.height + 20) + 'px';
547
+ break;
548
+
549
+ case 'bottom-left-aligned':
550
+ // Bottom-left-aligned is the same as the default bottom
551
+ case 'bottom':
552
+ // Bottom going to follow the default behavior
553
+ default:
554
+ arrowLayer.className = 'introjs-arrow top';
555
+
556
+ var tooltipLayerStyleLeft = 0;
557
+ _checkRight(targetOffset, tooltipLayerStyleLeft, tooltipOffset, windowSize, tooltipLayer);
558
+ tooltipLayer.style.top = (targetOffset.height + 20) + 'px';
559
+ break;
560
+ }
561
+ }
562
+
563
+ /**
564
+ * Set tooltip left so it doesn't go off the right side of the window
565
+ *
566
+ * @return boolean true, if tooltipLayerStyleLeft is ok. false, otherwise.
567
+ */
568
+ function _checkRight(targetOffset, tooltipLayerStyleLeft, tooltipOffset, windowSize, tooltipLayer) {
569
+ if (targetOffset.left + tooltipLayerStyleLeft + tooltipOffset.width > windowSize.width) {
570
+ // off the right side of the window
571
+ tooltipLayer.style.left = (windowSize.width - tooltipOffset.width - targetOffset.left) + 'px';
572
+ return false;
573
+ }
574
+ tooltipLayer.style.left = tooltipLayerStyleLeft + 'px';
575
+ return true;
576
+ }
577
+
578
+ /**
579
+ * Set tooltip right so it doesn't go off the left side of the window
580
+ *
581
+ * @return boolean true, if tooltipLayerStyleRight is ok. false, otherwise.
582
+ */
583
+ function _checkLeft(targetOffset, tooltipLayerStyleRight, tooltipOffset, tooltipLayer) {
584
+ if (targetOffset.left + targetOffset.width - tooltipLayerStyleRight - tooltipOffset.width < 0) {
585
+ // off the left side of the window
586
+ tooltipLayer.style.left = (-targetOffset.left) + 'px';
587
+ return false;
588
+ }
589
+ tooltipLayer.style.right = tooltipLayerStyleRight + 'px';
590
+ return true;
591
+ }
592
+
593
+ /**
594
+ * Determines the position of the tooltip based on the position precedence and availability
595
+ * of screen space.
596
+ *
597
+ * @param {Object} targetElement
598
+ * @param {Object} tooltipLayer
599
+ * @param {Object} desiredTooltipPosition
600
+ *
601
+ */
602
+ function _determineAutoPosition(targetElement, tooltipLayer, desiredTooltipPosition) {
603
+
604
+ // Take a clone of position precedence. These will be the available
605
+ var possiblePositions = this._options.positionPrecedence.slice();
606
+
607
+ var windowSize = _getWinSize();
608
+ var tooltipHeight = _getOffset(tooltipLayer).height + 10;
609
+ var tooltipWidth = _getOffset(tooltipLayer).width + 20;
610
+ var targetOffset = _getOffset(targetElement);
611
+
612
+ // If we check all the possible areas, and there are no valid places for the tooltip, the element
613
+ // must take up most of the screen real estate. Show the tooltip floating in the middle of the screen.
614
+ var calculatedPosition = "floating";
615
+
616
+ // Check if the width of the tooltip + the starting point would spill off the right side of the screen
617
+ // If no, neither bottom or top are valid
618
+ if (targetOffset.left + tooltipWidth > windowSize.width || ((targetOffset.left + (targetOffset.width / 2)) - tooltipWidth) < 0) {
619
+ _removeEntry(possiblePositions, "bottom");
620
+ _removeEntry(possiblePositions, "top");
621
+ } else {
622
+ // Check for space below
623
+ if ((targetOffset.height + targetOffset.top + tooltipHeight) > windowSize.height) {
624
+ _removeEntry(possiblePositions, "bottom");
625
+ }
626
+
627
+ // Check for space above
628
+ if (targetOffset.top - tooltipHeight < 0) {
629
+ _removeEntry(possiblePositions, "top");
630
+ }
631
+ }
632
+
633
+ // Check for space to the right
634
+ if (targetOffset.width + targetOffset.left + tooltipWidth > windowSize.width) {
635
+ _removeEntry(possiblePositions, "right");
636
+ }
637
+
638
+ // Check for space to the left
639
+ if (targetOffset.left - tooltipWidth < 0) {
640
+ _removeEntry(possiblePositions, "left");
641
+ }
642
+
643
+ // At this point, our array only has positions that are valid. Pick the first one, as it remains in order
644
+ if (possiblePositions.length > 0) {
645
+ calculatedPosition = possiblePositions[0];
646
+ }
647
+
648
+ // If the requested position is in the list, replace our calculated choice with that
649
+ if (desiredTooltipPosition && desiredTooltipPosition != "auto") {
650
+ if (possiblePositions.indexOf(desiredTooltipPosition) > -1) {
651
+ calculatedPosition = desiredTooltipPosition;
652
+ }
653
+ }
654
+
655
+ return calculatedPosition;
656
+ }
657
+
658
+ /**
659
+ * Remove an entry from a string array if it's there, does nothing if it isn't there.
660
+ *
661
+ * @param {Array} stringArray
662
+ * @param {String} stringToRemove
663
+ */
664
+ function _removeEntry(stringArray, stringToRemove) {
665
+ if (stringArray.indexOf(stringToRemove) > -1) {
666
+ stringArray.splice(stringArray.indexOf(stringToRemove), 1);
667
+ }
668
+ }
669
+
670
+ /**
671
+ * Update the position of the helper layer on the screen
672
+ *
673
+ * @api private
674
+ * @method _setHelperLayerPosition
675
+ * @param {Object} helperLayer
676
+ */
677
+ function _setHelperLayerPosition(helperLayer) {
678
+ if (helperLayer) {
679
+ //prevent error when `this._currentStep` in undefined
680
+ if (!this._introItems[this._currentStep]) return;
681
+
682
+ var currentElement = this._introItems[this._currentStep],
683
+ elementPosition = _getOffset(currentElement.element),
684
+ widthHeightPadding = 10;
685
+
686
+ if (currentElement.position == 'floating') {
687
+ widthHeightPadding = 0;
688
+ }
689
+
690
+ //set new position to helper layer
691
+ helperLayer.setAttribute('style', 'width: ' + (elementPosition.width + widthHeightPadding) + 'px; ' +
692
+ 'height:' + (elementPosition.height + widthHeightPadding) + 'px; ' +
693
+ 'top:' + (elementPosition.top - 5) + 'px;' +
694
+ 'left: ' + (elementPosition.left - 5) + 'px;');
695
+
696
+ }
697
+ }
698
+
699
+ /**
700
+ * Add disableinteraction layer and adjust the size and position of the layer
701
+ *
702
+ * @api private
703
+ * @method _disableInteraction
704
+ */
705
+ function _disableInteraction () {
706
+ var disableInteractionLayer = document.querySelector('.introjs-disableInteraction');
707
+ if (disableInteractionLayer === null) {
708
+ disableInteractionLayer = document.createElement('div');
709
+ disableInteractionLayer.className = 'introjs-disableInteraction';
710
+ this._targetElement.appendChild(disableInteractionLayer);
711
+ }
712
+
713
+ _setHelperLayerPosition.call(this, disableInteractionLayer);
714
+ }
715
+
716
+ /**
717
+ * Show an element on the page
718
+ *
719
+ * @api private
720
+ * @method _showElement
721
+ * @param {Object} targetElement
722
+ */
723
+ function _showElement(targetElement) {
724
+
725
+ if (typeof (this._introChangeCallback) !== 'undefined') {
726
+ this._introChangeCallback.call(this, targetElement.element);
727
+ }
728
+
729
+ var self = this,
730
+ oldHelperLayer = document.querySelector('.introjs-helperLayer'),
731
+ oldReferenceLayer = document.querySelector('.introjs-tooltipReferenceLayer'),
732
+ highlightClass = 'introjs-helperLayer',
733
+ elementPosition = _getOffset(targetElement.element);
734
+
735
+ //check for a current step highlight class
736
+ if (typeof (targetElement.highlightClass) === 'string') {
737
+ highlightClass += (' ' + targetElement.highlightClass);
738
+ }
739
+ //check for options highlight class
740
+ if (typeof (this._options.highlightClass) === 'string') {
741
+ highlightClass += (' ' + this._options.highlightClass);
742
+ }
743
+
744
+ if (oldHelperLayer != null) {
745
+ var oldHelperNumberLayer = oldReferenceLayer.querySelector('.introjs-helperNumberLayer'),
746
+ oldtooltipLayer = oldReferenceLayer.querySelector('.introjs-tooltiptext'),
747
+ oldArrowLayer = oldReferenceLayer.querySelector('.introjs-arrow'),
748
+ oldtooltipContainer = oldReferenceLayer.querySelector('.introjs-tooltip'),
749
+ skipTooltipButton = oldReferenceLayer.querySelector('.introjs-skipbutton'),
750
+ prevTooltipButton = oldReferenceLayer.querySelector('.introjs-prevbutton'),
751
+ nextTooltipButton = oldReferenceLayer.querySelector('.introjs-nextbutton');
752
+
753
+ //update or reset the helper highlight class
754
+ oldHelperLayer.className = highlightClass;
755
+ //hide the tooltip
756
+ oldtooltipContainer.style.opacity = 0;
757
+ oldtooltipContainer.style.display = "none";
758
+
759
+ if (oldHelperNumberLayer != null) {
760
+ var lastIntroItem = this._introItems[(targetElement.step - 2 >= 0 ? targetElement.step - 2 : 0)];
761
+
762
+ if (lastIntroItem != null && (this._direction == 'forward' && lastIntroItem.position == 'floating') || (this._direction == 'backward' && targetElement.position == 'floating')) {
763
+ oldHelperNumberLayer.style.opacity = 0;
764
+ }
765
+ }
766
+
767
+ //set new position to helper layer
768
+ _setHelperLayerPosition.call(self, oldHelperLayer);
769
+ _setHelperLayerPosition.call(self, oldReferenceLayer);
770
+
771
+ //remove `introjs-fixParent` class from the elements
772
+ var fixParents = document.querySelectorAll('.introjs-fixParent');
773
+ if (fixParents && fixParents.length > 0) {
774
+ for (var i = fixParents.length - 1; i >= 0; i--) {
775
+ fixParents[i].className = fixParents[i].className.replace(/introjs-fixParent/g, '').replace(/^\s+|\s+$/g, '');
776
+ };
777
+ }
778
+
779
+ //remove old classes
780
+ var oldShowElement = document.querySelector('.introjs-showElement');
781
+ oldShowElement.className = oldShowElement.className.replace(/introjs-[a-zA-Z]+/g, '').replace(/^\s+|\s+$/g, '');
782
+
783
+ //we should wait until the CSS3 transition is competed (it's 0.3 sec) to prevent incorrect `height` and `width` calculation
784
+ if (self._lastShowElementTimer) {
785
+ clearTimeout(self._lastShowElementTimer);
786
+ }
787
+ self._lastShowElementTimer = setTimeout(function() {
788
+ //set current step to the label
789
+ if (oldHelperNumberLayer != null) {
790
+ oldHelperNumberLayer.innerHTML = targetElement.step;
791
+ }
792
+ //set current tooltip text
793
+ oldtooltipLayer.innerHTML = targetElement.intro;
794
+ //set the tooltip position
795
+ oldtooltipContainer.style.display = "block";
796
+ _placeTooltip.call(self, targetElement.element, oldtooltipContainer, oldArrowLayer, oldHelperNumberLayer);
797
+
798
+ //change active bullet
799
+ oldReferenceLayer.querySelector('.introjs-bullets li > a.active').className = '';
800
+ oldReferenceLayer.querySelector('.introjs-bullets li > a[data-stepnumber="' + targetElement.step + '"]').className = 'active';
801
+
802
+ oldReferenceLayer.querySelector('.introjs-progress .introjs-progressbar').setAttribute('style', 'width:' + _getProgress.call(self) + '%;');
803
+
804
+ //show the tooltip
805
+ oldtooltipContainer.style.opacity = 1;
806
+ if (oldHelperNumberLayer) oldHelperNumberLayer.style.opacity = 1;
807
+
808
+ //reset button focus
809
+ if (nextTooltipButton.tabIndex === -1) {
810
+ //tabindex of -1 means we are at the end of the tour - focus on skip / done
811
+ skipTooltipButton.focus();
812
+ } else {
813
+ //still in the tour, focus on next
814
+ nextTooltipButton.focus();
815
+ }
816
+ }, 350);
817
+
818
+ } else {
819
+ var helperLayer = document.createElement('div'),
820
+ referenceLayer = document.createElement('div'),
821
+ arrowLayer = document.createElement('div'),
822
+ tooltipLayer = document.createElement('div'),
823
+ tooltipTextLayer = document.createElement('div'),
824
+ bulletsLayer = document.createElement('div'),
825
+ progressLayer = document.createElement('div'),
826
+ buttonsLayer = document.createElement('div');
827
+
828
+ helperLayer.className = highlightClass;
829
+ referenceLayer.className = 'introjs-tooltipReferenceLayer';
830
+
831
+ //set new position to helper layer
832
+ _setHelperLayerPosition.call(self, helperLayer);
833
+ _setHelperLayerPosition.call(self, referenceLayer);
834
+
835
+ //add helper layer to target element
836
+ this._targetElement.appendChild(helperLayer);
837
+ this._targetElement.appendChild(referenceLayer);
838
+
839
+ arrowLayer.className = 'introjs-arrow';
840
+
841
+ tooltipTextLayer.className = 'introjs-tooltiptext';
842
+ tooltipTextLayer.innerHTML = targetElement.intro;
843
+
844
+ bulletsLayer.className = 'introjs-bullets';
845
+
846
+ if (this._options.showBullets === false) {
847
+ bulletsLayer.style.display = 'none';
848
+ }
849
+
850
+ var ulContainer = document.createElement('ul');
851
+
852
+ for (var i = 0, stepsLength = this._introItems.length; i < stepsLength; i++) {
853
+ var innerLi = document.createElement('li');
854
+ var anchorLink = document.createElement('a');
855
+
856
+ anchorLink.onclick = function() {
857
+ self.goToStep(this.getAttribute('data-stepnumber'));
858
+ };
859
+
860
+ if (i === (targetElement.step-1)) anchorLink.className = 'active';
861
+
862
+ anchorLink.href = 'javascript:void(0);';
863
+ anchorLink.innerHTML = "&nbsp;";
864
+ anchorLink.setAttribute('data-stepnumber', this._introItems[i].step);
865
+
866
+ innerLi.appendChild(anchorLink);
867
+ ulContainer.appendChild(innerLi);
868
+ }
869
+
870
+ bulletsLayer.appendChild(ulContainer);
871
+
872
+ progressLayer.className = 'introjs-progress';
873
+
874
+ if (this._options.showProgress === false) {
875
+ progressLayer.style.display = 'none';
876
+ }
877
+ var progressBar = document.createElement('div');
878
+ progressBar.className = 'introjs-progressbar';
879
+ progressBar.setAttribute('style', 'width:' + _getProgress.call(this) + '%;');
880
+
881
+ progressLayer.appendChild(progressBar);
882
+
883
+ buttonsLayer.className = 'introjs-tooltipbuttons';
884
+ if (this._options.showButtons === false) {
885
+ buttonsLayer.style.display = 'none';
886
+ }
887
+
888
+ tooltipLayer.className = 'introjs-tooltip';
889
+ tooltipLayer.appendChild(tooltipTextLayer);
890
+ tooltipLayer.appendChild(bulletsLayer);
891
+ tooltipLayer.appendChild(progressLayer);
892
+
893
+ //add helper layer number
894
+ if (this._options.showStepNumbers == true) {
895
+ var helperNumberLayer = document.createElement('span');
896
+ helperNumberLayer.className = 'introjs-helperNumberLayer';
897
+ helperNumberLayer.innerHTML = targetElement.step;
898
+ referenceLayer.appendChild(helperNumberLayer);
899
+ }
900
+
901
+ tooltipLayer.appendChild(arrowLayer);
902
+ referenceLayer.appendChild(tooltipLayer);
903
+
904
+ //next button
905
+ var nextTooltipButton = document.createElement('a');
906
+
907
+ nextTooltipButton.onclick = function() {
908
+ if (self._introItems.length - 1 != self._currentStep) {
909
+ _nextStep.call(self);
910
+ }
911
+ };
912
+
913
+ nextTooltipButton.href = 'javascript:void(0);';
914
+ nextTooltipButton.innerHTML = this._options.nextLabel;
915
+
916
+ //previous button
917
+ var prevTooltipButton = document.createElement('a');
918
+
919
+ prevTooltipButton.onclick = function() {
920
+ if (self._currentStep != 0) {
921
+ _previousStep.call(self);
922
+ }
923
+ };
924
+
925
+ prevTooltipButton.href = 'javascript:void(0);';
926
+ prevTooltipButton.innerHTML = this._options.prevLabel;
927
+
928
+ //skip button
929
+ var skipTooltipButton = document.createElement('a');
930
+ skipTooltipButton.className = 'introjs-button introjs-skipbutton btn';
931
+ skipTooltipButton.href = 'javascript:void(0);';
932
+ skipTooltipButton.innerHTML = this._options.skipLabel;
933
+
934
+ skipTooltipButton.onclick = function() {
935
+ if (self._introItems.length - 1 == self._currentStep && typeof (self._introCompleteCallback) === 'function') {
936
+ self._introCompleteCallback.call(self);
937
+ }
938
+
939
+ if (self._introItems.length - 1 != self._currentStep && typeof (self._introExitCallback) === 'function') {
940
+ self._introExitCallback.call(self);
941
+ }
942
+
943
+ _exitIntro.call(self, self._targetElement);
944
+ };
945
+
946
+ buttonsLayer.appendChild(skipTooltipButton);
947
+
948
+ //in order to prevent displaying next/previous button always
949
+ if (this._introItems.length > 1) {
950
+ buttonsLayer.appendChild(prevTooltipButton);
951
+ buttonsLayer.appendChild(nextTooltipButton);
952
+ }
953
+
954
+ tooltipLayer.appendChild(buttonsLayer);
955
+
956
+ //set proper position
957
+ _placeTooltip.call(self, targetElement.element, tooltipLayer, arrowLayer, helperNumberLayer);
958
+ }
959
+
960
+ //disable interaction
961
+ if (this._options.disableInteraction === true) {
962
+ _disableInteraction.call(self);
963
+ }
964
+
965
+ prevTooltipButton.removeAttribute('tabIndex');
966
+ nextTooltipButton.removeAttribute('tabIndex');
967
+
968
+ if (this._currentStep == 0 && this._introItems.length > 1) {
969
+ prevTooltipButton.className = 'introjs-button introjs-prevbutton introjs-disabled disabled btn btn-success';
970
+ prevTooltipButton.tabIndex = '-1';
971
+ nextTooltipButton.className = 'introjs-button introjs-nextbutton btn btn-primary';
972
+ skipTooltipButton.innerHTML = this._options.skipLabel;
973
+ } else if (this._introItems.length - 1 == this._currentStep || this._introItems.length == 1) {
974
+ skipTooltipButton.innerHTML = this._options.doneLabel;
975
+ prevTooltipButton.className = 'introjs-button introjs-prevbutton btn btn-success';
976
+ nextTooltipButton.className = 'introjs-button introjs-nextbutton introjs-disabled disabled btn btn-primary';
977
+ nextTooltipButton.tabIndex = '-1';
978
+ } else {
979
+ prevTooltipButton.className = 'introjs-button introjs-prevbutton btn btn-success';
980
+ nextTooltipButton.className = 'introjs-button introjs-nextbutton btn btn-primary';
981
+ skipTooltipButton.innerHTML = this._options.skipLabel;
982
+ }
983
+
984
+ //Set focus on "next" button, so that hitting Enter always moves you onto the next step
985
+ nextTooltipButton.focus();
986
+
987
+ //add target element position style
988
+ targetElement.element.className += ' introjs-showElement';
989
+
990
+ var currentElementPosition = _getPropValue(targetElement.element, 'position');
991
+ if (currentElementPosition !== 'absolute' &&
992
+ currentElementPosition !== 'relative') {
993
+ //change to new intro item
994
+ targetElement.element.className += ' introjs-relativePosition';
995
+ }
996
+
997
+ var parentElm = targetElement.element.parentNode;
998
+ while (parentElm != null) {
999
+ if (parentElm.tagName.toLowerCase() === 'body') break;
1000
+
1001
+ //fix The Stacking Contenxt problem.
1002
+ //More detail: https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context
1003
+ var zIndex = _getPropValue(parentElm, 'z-index');
1004
+ var opacity = parseFloat(_getPropValue(parentElm, 'opacity'));
1005
+ var transform = _getPropValue(parentElm, 'transform') || _getPropValue(parentElm, '-webkit-transform') || _getPropValue(parentElm, '-moz-transform') || _getPropValue(parentElm, '-ms-transform') || _getPropValue(parentElm, '-o-transform');
1006
+ if (/[0-9]+/.test(zIndex) || opacity < 1 || (transform !== 'none' && transform !== undefined)) {
1007
+ parentElm.className += ' introjs-fixParent';
1008
+ }
1009
+
1010
+ parentElm = parentElm.parentNode;
1011
+ }
1012
+
1013
+ if (!_elementInViewport(targetElement.element) && this._options.scrollToElement === true) {
1014
+ var rect = targetElement.element.getBoundingClientRect(),
1015
+ winHeight = _getWinSize().height,
1016
+ top = rect.bottom - (rect.bottom - rect.top),
1017
+ bottom = rect.bottom - winHeight;
1018
+
1019
+ //Scroll up
1020
+ if (top < 0 || targetElement.element.clientHeight > winHeight) {
1021
+ window.scrollBy(0, top - 30); // 30px padding from edge to look nice
1022
+
1023
+ //Scroll down
1024
+ } else {
1025
+ window.scrollBy(0, bottom + 100); // 70px + 30px padding from edge to look nice
1026
+ }
1027
+ }
1028
+
1029
+ if (typeof (this._introAfterChangeCallback) !== 'undefined') {
1030
+ this._introAfterChangeCallback.call(this, targetElement.element);
1031
+ }
1032
+ }
1033
+
1034
+ /**
1035
+ * Get an element CSS property on the page
1036
+ * Thanks to JavaScript Kit: http://www.javascriptkit.com/dhtmltutors/dhtmlcascade4.shtml
1037
+ *
1038
+ * @api private
1039
+ * @method _getPropValue
1040
+ * @param {Object} element
1041
+ * @param {String} propName
1042
+ * @returns Element's property value
1043
+ */
1044
+ function _getPropValue (element, propName) {
1045
+ var propValue = '';
1046
+ if (element.currentStyle) { //IE
1047
+ propValue = element.currentStyle[propName];
1048
+ } else if (document.defaultView && document.defaultView.getComputedStyle) { //Others
1049
+ propValue = document.defaultView.getComputedStyle(element, null).getPropertyValue(propName);
1050
+ }
1051
+
1052
+ //Prevent exception in IE
1053
+ if (propValue && propValue.toLowerCase) {
1054
+ return propValue.toLowerCase();
1055
+ } else {
1056
+ return propValue;
1057
+ }
1058
+ }
1059
+
1060
+ /**
1061
+ * Provides a cross-browser way to get the screen dimensions
1062
+ * via: http://stackoverflow.com/questions/5864467/internet-explorer-innerheight
1063
+ *
1064
+ * @api private
1065
+ * @method _getWinSize
1066
+ * @returns {Object} width and height attributes
1067
+ */
1068
+ function _getWinSize() {
1069
+ if (window.innerWidth != undefined) {
1070
+ return { width: window.innerWidth, height: window.innerHeight };
1071
+ } else {
1072
+ var D = document.documentElement;
1073
+ return { width: D.clientWidth, height: D.clientHeight };
1074
+ }
1075
+ }
1076
+
1077
+ /**
1078
+ * Add overlay layer to the page
1079
+ * http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport
1080
+ *
1081
+ * @api private
1082
+ * @method _elementInViewport
1083
+ * @param {Object} el
1084
+ */
1085
+ function _elementInViewport(el) {
1086
+ var rect = el.getBoundingClientRect();
1087
+
1088
+ return (
1089
+ rect.top >= 0 &&
1090
+ rect.left >= 0 &&
1091
+ (rect.bottom+80) <= window.innerHeight && // add 80 to get the text right
1092
+ rect.right <= window.innerWidth
1093
+ );
1094
+ }
1095
+
1096
+ /**
1097
+ * Add overlay layer to the page
1098
+ *
1099
+ * @api private
1100
+ * @method _addOverlayLayer
1101
+ * @param {Object} targetElm
1102
+ */
1103
+ function _addOverlayLayer(targetElm) {
1104
+ var overlayLayer = document.createElement('div'),
1105
+ styleText = '',
1106
+ self = this;
1107
+
1108
+ //set css class name
1109
+ overlayLayer.className = 'introjs-overlay';
1110
+
1111
+ //check if the target element is body, we should calculate the size of overlay layer in a better way
1112
+ if (targetElm.tagName.toLowerCase() === 'body') {
1113
+ styleText += 'top: 0;bottom: 0; left: 0;right: 0;position: fixed;';
1114
+ overlayLayer.setAttribute('style', styleText);
1115
+ } else {
1116
+ //set overlay layer position
1117
+ var elementPosition = _getOffset(targetElm);
1118
+ if (elementPosition) {
1119
+ styleText += 'width: ' + elementPosition.width + 'px; height:' + elementPosition.height + 'px; top:' + elementPosition.top + 'px;left: ' + elementPosition.left + 'px;';
1120
+ overlayLayer.setAttribute('style', styleText);
1121
+ }
1122
+ }
1123
+
1124
+ targetElm.appendChild(overlayLayer);
1125
+
1126
+ overlayLayer.onclick = function() {
1127
+ if (self._options.exitOnOverlayClick == true) {
1128
+
1129
+ //check if any callback is defined
1130
+ if (self._introExitCallback != undefined) {
1131
+ self._introExitCallback.call(self);
1132
+ }
1133
+ _exitIntro.call(self, targetElm);
1134
+ }
1135
+ };
1136
+
1137
+ setTimeout(function() {
1138
+ styleText += 'opacity: ' + self._options.overlayOpacity.toString() + ';';
1139
+ overlayLayer.setAttribute('style', styleText);
1140
+ }, 10);
1141
+
1142
+ return true;
1143
+ }
1144
+
1145
+ /**
1146
+ * Get an element position on the page
1147
+ * Thanks to `meouw`: http://stackoverflow.com/a/442474/375966
1148
+ *
1149
+ * @api private
1150
+ * @method _getOffset
1151
+ * @param {Object} element
1152
+ * @returns Element's position info
1153
+ */
1154
+ function _getOffset(element) {
1155
+ var elementPosition = {};
1156
+
1157
+ //set width
1158
+ elementPosition.width = element.offsetWidth;
1159
+
1160
+ //set height
1161
+ elementPosition.height = element.offsetHeight;
1162
+
1163
+ //calculate element top and left
1164
+ var _x = 0;
1165
+ var _y = 0;
1166
+ while (element && !isNaN(element.offsetLeft) && !isNaN(element.offsetTop)) {
1167
+ _x += element.offsetLeft;
1168
+ _y += element.offsetTop;
1169
+ element = element.offsetParent;
1170
+ }
1171
+ //set top
1172
+ elementPosition.top = _y;
1173
+ //set left
1174
+ elementPosition.left = _x;
1175
+
1176
+ return elementPosition;
1177
+ }
1178
+
1179
+ /**
1180
+ * Gets the current progress percentage
1181
+ *
1182
+ * @api private
1183
+ * @method _getProgress
1184
+ * @returns current progress percentage
1185
+ */
1186
+ function _getProgress() {
1187
+ // Steps are 0 indexed
1188
+ var currentStep = parseInt((this._currentStep + 1), 10);
1189
+ return ((currentStep / this._introItems.length) * 100);
1190
+ }
1191
+
1192
+ /**
1193
+ * Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1
1194
+ * via: http://stackoverflow.com/questions/171251/how-can-i-merge-properties-of-two-javascript-objects-dynamically
1195
+ *
1196
+ * @param obj1
1197
+ * @param obj2
1198
+ * @returns obj3 a new object based on obj1 and obj2
1199
+ */
1200
+ function _mergeOptions(obj1,obj2) {
1201
+ var obj3 = {};
1202
+ for (var attrname in obj1) { obj3[attrname] = obj1[attrname]; }
1203
+ for (var attrname in obj2) { obj3[attrname] = obj2[attrname]; }
1204
+ return obj3;
1205
+ }
1206
+
1207
+ var introJs = function (targetElm) {
1208
+ if (typeof (targetElm) === 'object') {
1209
+ //Ok, create a new instance
1210
+ return new IntroJs(targetElm);
1211
+
1212
+ } else if (typeof (targetElm) === 'string') {
1213
+ //select the target element with query selector
1214
+ var targetElement = document.querySelector(targetElm);
1215
+
1216
+ if (targetElement) {
1217
+ return new IntroJs(targetElement);
1218
+ } else {
1219
+ throw new Error('There is no element with given selector.');
1220
+ }
1221
+ } else {
1222
+ return new IntroJs(document.body);
1223
+ }
1224
+ };
1225
+
1226
+ /**
1227
+ * Current IntroJs version
1228
+ *
1229
+ * @property version
1230
+ * @type String
1231
+ */
1232
+ introJs.version = VERSION;
1233
+
1234
+ //Prototype
1235
+ introJs.fn = IntroJs.prototype = {
1236
+ clone: function () {
1237
+ return new IntroJs(this);
1238
+ },
1239
+ setOption: function(option, value) {
1240
+ this._options[option] = value;
1241
+ return this;
1242
+ },
1243
+ setOptions: function(options) {
1244
+ this._options = _mergeOptions(this._options, options);
1245
+ return this;
1246
+ },
1247
+ start: function () {
1248
+ _introForElement.call(this, this._targetElement);
1249
+ return this;
1250
+ },
1251
+ goToStep: function(step) {
1252
+ _goToStep.call(this, step);
1253
+ return this;
1254
+ },
1255
+ nextStep: function() {
1256
+ _nextStep.call(this);
1257
+ return this;
1258
+ },
1259
+ previousStep: function() {
1260
+ _previousStep.call(this);
1261
+ return this;
1262
+ },
1263
+ exit: function() {
1264
+ _exitIntro.call(this, this._targetElement);
1265
+ return this;
1266
+ },
1267
+ refresh: function() {
1268
+ _setHelperLayerPosition.call(this, document.querySelector('.introjs-helperLayer'));
1269
+ _setHelperLayerPosition.call(this, document.querySelector('.introjs-tooltipReferenceLayer'));
1270
+ return this;
1271
+ },
1272
+ onbeforechange: function(providedCallback) {
1273
+ if (typeof (providedCallback) === 'function') {
1274
+ this._introBeforeChangeCallback = providedCallback;
1275
+ } else {
1276
+ throw new Error('Provided callback for onbeforechange was not a function');
1277
+ }
1278
+ return this;
1279
+ },
1280
+ onchange: function(providedCallback) {
1281
+ if (typeof (providedCallback) === 'function') {
1282
+ this._introChangeCallback = providedCallback;
1283
+ } else {
1284
+ throw new Error('Provided callback for onchange was not a function.');
1285
+ }
1286
+ return this;
1287
+ },
1288
+ onafterchange: function(providedCallback) {
1289
+ if (typeof (providedCallback) === 'function') {
1290
+ this._introAfterChangeCallback = providedCallback;
1291
+ } else {
1292
+ throw new Error('Provided callback for onafterchange was not a function');
1293
+ }
1294
+ return this;
1295
+ },
1296
+ oncomplete: function(providedCallback) {
1297
+ if (typeof (providedCallback) === 'function') {
1298
+ this._introCompleteCallback = providedCallback;
1299
+ } else {
1300
+ throw new Error('Provided callback for oncomplete was not a function.');
1301
+ }
1302
+ return this;
1303
+ },
1304
+ onexit: function(providedCallback) {
1305
+ if (typeof (providedCallback) === 'function') {
1306
+ this._introExitCallback = providedCallback;
1307
+ } else {
1308
+ throw new Error('Provided callback for onexit was not a function.');
1309
+ }
1310
+ return this;
1311
+ }
1312
+ };
1313
+
1314
+ exports.introJs = introJs;
1315
+ return introJs;
1316
+ }));