activeadmin-settings 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. data/.gitignore +1 -0
  2. data/README.md +11 -1
  3. data/activeadmin-settings.gemspec +0 -3
  4. data/app/controller/activeadmin_settings/admin_users_controller.rb +27 -0
  5. data/app/controller/activeadmin_settings/settings_controller.rb +12 -0
  6. data/app/views/admin/settings/_admin.html.erb +42 -0
  7. data/app/views/admin/settings/_admin_form.html.erb +22 -0
  8. data/app/views/admin/settings/_admins_table.html.erb +16 -0
  9. data/app/views/admin/settings/_index.html.erb +20 -0
  10. data/app/views/admin/settings/_settings_table.html.erb +48 -0
  11. data/config/routes.rb +4 -0
  12. data/lib/activeadmin-settings.rb +22 -3
  13. data/lib/activeadmin-settings/helper.rb +5 -2
  14. data/lib/activeadmin-settings/version.rb +1 -1
  15. data/lib/generators/activeadmin_settings/install_generator.rb +4 -5
  16. data/lib/generators/activeadmin_settings/templates/admin/settings.rb +17 -38
  17. data/lib/generators/activeadmin_settings/templates/config/activeadmin_settings.yml +8 -6
  18. data/lib/generators/activeadmin_settings/templates/models/setting.rb +3 -3
  19. data/lib/tasks/activeadmin_settings.rake +15 -0
  20. data/vendor/assets/javascripts/activeadmin_settings.js.coffee +2 -32
  21. data/vendor/assets/javascripts/activeadmin_settings/admins.js.coffee +80 -0
  22. data/vendor/assets/javascripts/activeadmin_settings/flashes.js.coffee +6 -0
  23. data/vendor/assets/javascripts/activeadmin_settings/settings.js.coffee +32 -0
  24. data/vendor/assets/javascripts/activeadmin_settings/tabs.js.coffee +6 -0
  25. data/vendor/assets/javascripts/jquery.easytabs.js +679 -0
  26. data/vendor/assets/stylesheets/activeadmin_settings.css.scss +14 -1
  27. metadata +16 -5
  28. data/app/views/admin/settings/_value.html.erb +0 -21
  29. data/lib/generators/activeadmin_settings/templates/admin/admin_users.rb +0 -23
@@ -1,33 +1,3 @@
1
1
  #= require jquery.form
2
-
3
- $ ->
4
- $('#settings .form form').submit ->
5
- false
6
-
7
- $('.edit_setting_link').click (e) ->
8
- e.preventDefault()
9
- id = $(this).attr "data-setting-id"
10
- setting = $("#setting_#{id}")
11
- setting.find(".value").hide()
12
- setting.find(".form").show()
13
- setting.find(".update_setting_link").show()
14
- $(this).hide()
15
-
16
- $('.update_setting_link').click (e) ->
17
- e.preventDefault()
18
- btn = $(this)
19
- id = btn.attr "data-setting-id"
20
- setting = $("#setting_#{id}")
21
- setting.find(".form").hide()
22
-
23
- # submit form, should use jquery form for file submissions
24
- form = setting.find(".form form")
25
- form.submit =>
26
- form.ajaxSubmit
27
- success: (value) =>
28
- setting.find('.value').html(value)
29
- setting.find(".value").show()
30
- setting.find(".edit_setting_link").show()
31
- btn.hide()
32
- false
33
- form.submit()
2
+ #= require jquery.easytabs
3
+ #= require_tree ./activeadmin_settings
@@ -0,0 +1,80 @@
1
+ # Admins
2
+ $ ->
3
+ $('.new_admin #admin_user_password_confirmation').keypress (e) ->
4
+ if e.which == 13
5
+ e.preventDefault()
6
+ $("#admins .create_link").click()
7
+
8
+ $('tr.admin #admin_user_password_confirmation').keypress (e) ->
9
+ alert 'kuku'
10
+ if e.which == 13
11
+ e.preventDefault()
12
+ $(this).closest("tr.admin").find(".update_link").click()
13
+
14
+ # Credentials
15
+ hide_credentials_form = (tr) ->
16
+ tr.find(".form").hide()
17
+ tr.find(".admin_actions").show()
18
+ tr.find(".form_actions").hide()
19
+
20
+ show_credentials_form = (tr) ->
21
+ tr.find(".form").show()
22
+ tr.find(".admin_actions").hide()
23
+ tr.find(".form_actions").show()
24
+
25
+ $("#admins .admin .credentials_link").live "click", (e) ->
26
+ e.preventDefault()
27
+ btn = $(this)
28
+ tr = $(btn.attr("data-id"))
29
+ show_credentials_form(tr)
30
+
31
+ $("#admins .admin .cancel_link").live "click", (e) ->
32
+ e.preventDefault()
33
+ btn = $(this)
34
+ tr = $(btn.attr("data-id"))
35
+ hide_credentials_form(tr)
36
+
37
+ $("#admins .admin .update_link").live "click", (e) ->
38
+ e.preventDefault()
39
+ btn = $(this)
40
+ tr = $(btn.attr("data-id"))
41
+
42
+ form = tr.find(".form form")
43
+ form.ajaxSubmit
44
+ error: (res) =>
45
+ # parse errors
46
+ success: (res) =>
47
+ hide_credentials_form(tr)
48
+ form.clearForm()
49
+
50
+ # New admin
51
+ show_new_admin_form = ->
52
+ $("#admins .form.new_admin").show()
53
+ $("#admins .new_admin_actions .form_actions").show()
54
+ $("#admins .new_admin_actions .new_link").hide()
55
+ $("#admins .form.new_admin form").clearForm()
56
+ $("#admin_user_email").focus()
57
+
58
+ hide_new_admin_form = ->
59
+ $("#admins .form.new_admin").hide()
60
+ $("#admins .new_admin_actions .form_actions").hide()
61
+ $("#admins .new_admin_actions .new_link").show()
62
+
63
+ $("#admins .new_link").click (e) ->
64
+ e.preventDefault()
65
+ show_new_admin_form()
66
+
67
+ $(".new_admin_actions .cancel_link").click (e) ->
68
+ e.preventDefault()
69
+ hide_new_admin_form()
70
+
71
+ $("#admins .create_link").click (e) ->
72
+ e.preventDefault()
73
+ form = $("#admins .form.new_admin form")
74
+ form.ajaxSubmit
75
+ error: (res) =>
76
+ # parse errors
77
+ success: (html) =>
78
+ $("#admins tr.admin:last").after(html)
79
+ show_new_admin_form()
80
+
@@ -0,0 +1,6 @@
1
+ # Flashes
2
+ $ ->
3
+ hideFlashes = ->
4
+ $('.flashes').hide()
5
+
6
+ setTimeout(hideFlashes, 1000)
@@ -0,0 +1,32 @@
1
+ # Settings
2
+ $ ->
3
+ show_update_btn = (el) ->
4
+ $(el).closest('tr').find('.update_link').show()
5
+
6
+ hide_update_btn = (el) ->
7
+ $(el).closest('tr').find('.update_link').hide()
8
+
9
+ $('#settings .form form input, textarea').change ->
10
+ show_update_btn(this)
11
+
12
+ $('#settings .form form input, textarea').keypress ->
13
+ show_update_btn(this)
14
+
15
+ $('#settings .form form').submit ->
16
+ false
17
+
18
+ $('#settings .update_link').hide()
19
+
20
+ $('#settings .update_link').click (e) ->
21
+ e.preventDefault()
22
+ btn = $(this)
23
+ tr = $(btn.attr("data-id"))
24
+
25
+ form = tr.find(".form form")
26
+ form.ajaxSubmit
27
+ success: (value) =>
28
+ if form.parent().hasClass("file")
29
+ form.find(".inline-hints").html(value)
30
+ form.clearForm()
31
+ btn.hide()
32
+
@@ -0,0 +1,6 @@
1
+ # Tabs
2
+ $ ->
3
+ $('#settings_tabs').easytabs
4
+ animate:true
5
+ animationSpeed:0
6
+ tabActiveClass:"selected"
@@ -0,0 +1,679 @@
1
+ /*
2
+ * jQuery EasyTabs plugin 3.1.1
3
+ *
4
+ * Copyright (c) 2010-2011 Steve Schwartz (JangoSteve)
5
+ *
6
+ * Dual licensed under the MIT and GPL licenses:
7
+ * http://www.opensource.org/licenses/mit-license.php
8
+ * http://www.gnu.org/licenses/gpl.html
9
+ *
10
+ * Date: Tue Jan 26 16:30:00 2012 -0500
11
+ */
12
+ ( function($) {
13
+
14
+ $.easytabs = function(container, options) {
15
+
16
+ // Attach to plugin anything that should be available via
17
+ // the $container.data('easytabs') object
18
+ var plugin = this,
19
+ $container = $(container),
20
+
21
+ defaults = {
22
+ animate: true,
23
+ panelActiveClass: "active",
24
+ tabActiveClass: "active",
25
+ defaultTab: "li:first-child",
26
+ animationSpeed: "normal",
27
+ tabs: "> ul > li",
28
+ updateHash: true,
29
+ cycle: false,
30
+ collapsible: false,
31
+ collapsedClass: "collapsed",
32
+ collapsedByDefault: true,
33
+ uiTabs: false,
34
+ transitionIn: 'fadeIn',
35
+ transitionOut: 'fadeOut',
36
+ transitionInEasing: 'swing',
37
+ transitionOutEasing: 'swing',
38
+ transitionCollapse: 'slideUp',
39
+ transitionUncollapse: 'slideDown',
40
+ transitionCollapseEasing: 'swing',
41
+ transitionUncollapseEasing: 'swing',
42
+ containerClass: "",
43
+ tabsClass: "",
44
+ tabClass: "",
45
+ panelClass: "",
46
+ cache: true,
47
+ panelContext: $container
48
+ },
49
+
50
+ // Internal instance variables
51
+ // (not available via easytabs object)
52
+ $defaultTab,
53
+ $defaultTabLink,
54
+ transitions,
55
+ lastHash,
56
+ skipUpdateToHash,
57
+ animationSpeeds = {
58
+ fast: 200,
59
+ normal: 400,
60
+ slow: 600
61
+ },
62
+
63
+ // Shorthand variable so that we don't need to call
64
+ // plugin.settings throughout the plugin code
65
+ settings;
66
+
67
+ // =============================================================
68
+ // Functions available via easytabs object
69
+ // =============================================================
70
+
71
+ plugin.init = function() {
72
+
73
+ plugin.settings = settings = $.extend({}, defaults, options);
74
+
75
+ // Add jQuery UI's crazy class names to markup,
76
+ // so that markup will match theme CSS
77
+ if ( settings.uiTabs ) {
78
+ settings.tabActiveClass = 'ui-tabs-selected';
79
+ settings.containerClass = 'ui-tabs ui-widget ui-widget-content ui-corner-all';
80
+ settings.tabsClass = 'ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all';
81
+ settings.tabClass = 'ui-state-default ui-corner-top';
82
+ settings.panelClass = 'ui-tabs-panel ui-widget-content ui-corner-bottom';
83
+ }
84
+
85
+ // If collapsible is true and defaultTab specified, assume user wants defaultTab showing (not collapsed)
86
+ if ( settings.collapsible && options.defaultTab !== undefined && options.collpasedByDefault === undefined ) {
87
+ settings.collapsedByDefault = false;
88
+ }
89
+
90
+ // Convert 'normal', 'fast', and 'slow' animation speed settings to their respective speed in milliseconds
91
+ if ( typeof(settings.animationSpeed) === 'string' ) {
92
+ settings.animationSpeed = animationSpeeds[settings.animationSpeed];
93
+ }
94
+
95
+ $('a.anchor').remove().prependTo('body');
96
+
97
+ // Store easytabs object on container so we can easily set
98
+ // properties throughout
99
+ $container.data('easytabs', {});
100
+
101
+ plugin.setTransitions();
102
+
103
+ plugin.getTabs();
104
+
105
+ addClasses();
106
+
107
+ setDefaultTab();
108
+
109
+ bindToTabClicks();
110
+
111
+ initHashChange();
112
+
113
+ initCycle();
114
+
115
+ // Append data-easytabs HTML attribute to make easy to query for
116
+ // easytabs instances via CSS pseudo-selector
117
+ $container.attr('data-easytabs', true);
118
+ };
119
+
120
+ // Set transitions for switching between tabs based on options.
121
+ // Could be used to update transitions if settings are changes.
122
+ plugin.setTransitions = function() {
123
+ transitions = ( settings.animate ) ? {
124
+ show: settings.transitionIn,
125
+ hide: settings.transitionOut,
126
+ speed: settings.animationSpeed,
127
+ collapse: settings.transitionCollapse,
128
+ uncollapse: settings.transitionUncollapse,
129
+ halfSpeed: settings.animationSpeed / 2
130
+ } :
131
+ {
132
+ show: "show",
133
+ hide: "hide",
134
+ speed: 0,
135
+ collapse: "hide",
136
+ uncollapse: "show",
137
+ halfSpeed: 0
138
+ };
139
+ };
140
+
141
+ // Find and instantiate tabs and panels.
142
+ // Could be used to reset tab and panel collection if markup is
143
+ // modified.
144
+ plugin.getTabs = function() {
145
+ var $matchingPanel;
146
+
147
+ // Find the initial set of elements matching the setting.tabs
148
+ // CSS selector within the container
149
+ plugin.tabs = $container.find(settings.tabs),
150
+
151
+ // Instantiate panels as empty jquery object
152
+ plugin.panels = $(),
153
+
154
+ plugin.tabs.each(function(){
155
+ var $tab = $(this),
156
+ $a = $tab.children('a'),
157
+
158
+ // targetId is the ID of the panel, which is either the
159
+ // `href` attribute for non-ajax tabs, or in the
160
+ // `data-target` attribute for ajax tabs since the `href` is
161
+ // the ajax URL
162
+ targetId = $tab.children('a').data('target');
163
+
164
+ $tab.data('easytabs', {});
165
+
166
+ // If the tab has a `data-target` attribute, and is thus an ajax tab
167
+ if ( targetId !== undefined && targetId !== null ) {
168
+ $tab.data('easytabs').ajax = $a.attr('href');
169
+ } else {
170
+ targetId = $a.attr('href');
171
+ }
172
+ targetId = targetId.match(/#([^\?]+)/)[0].substr(1);
173
+
174
+ $matchingPanel = settings.panelContext.find("#" + targetId);
175
+
176
+ // If tab has a matching panel, add it to panels
177
+ if ( $matchingPanel.length ) {
178
+
179
+ // Store panel height before hiding
180
+ $matchingPanel.data('easytabs', {
181
+ position: $matchingPanel.css('position'),
182
+ visibility: $matchingPanel.css('visibility')
183
+ });
184
+
185
+ // Don't hide panel if it's active (allows `getTabs` to be called manually to re-instantiate tab collection)
186
+ $matchingPanel.not(settings.panelActiveClass).hide();
187
+
188
+ plugin.panels = plugin.panels.add($matchingPanel);
189
+
190
+ $tab.data('easytabs').panel = $matchingPanel;
191
+
192
+ // Otherwise, remove tab from tabs collection
193
+ } else {
194
+ plugin.tabs = plugin.tabs.not($tab);
195
+ }
196
+ });
197
+ };
198
+
199
+ // Select tab and fire callback
200
+ plugin.selectTab = function($clicked, callback) {
201
+ var url = window.location,
202
+ hash = url.hash.match(/^[^\?]*/)[0],
203
+ $targetPanel = $clicked.parent().data('easytabs').panel,
204
+ ajaxUrl = $clicked.parent().data('easytabs').ajax;
205
+
206
+ // Tab is collapsible and active => toggle collapsed state
207
+ if( settings.collapsible && ! skipUpdateToHash && ($clicked.hasClass(settings.tabActiveClass) || $clicked.hasClass(settings.collapsedClass)) ) {
208
+ plugin.toggleTabCollapse($clicked, $targetPanel, ajaxUrl, callback);
209
+
210
+ // Tab is not active and panel is not active => select tab
211
+ } else if( ! $clicked.hasClass(settings.tabActiveClass) || ! $targetPanel.hasClass(settings.panelActiveClass) ){
212
+ activateTab($clicked, $targetPanel, ajaxUrl, callback);
213
+
214
+ // Cache is disabled => reload (e.g reload an ajax tab).
215
+ } else if ( ! settings.cache ){
216
+ activateTab($clicked, $targetPanel, ajaxUrl, callback);
217
+ }
218
+
219
+ };
220
+
221
+ // Toggle tab collapsed state and fire callback
222
+ plugin.toggleTabCollapse = function($clicked, $targetPanel, ajaxUrl, callback) {
223
+ plugin.panels.stop(true,true);
224
+
225
+ if( fire($container,"easytabs:before", [$clicked, $targetPanel, settings]) ){
226
+ plugin.tabs.filter("." + settings.tabActiveClass).removeClass(settings.tabActiveClass).children().removeClass(settings.tabActiveClass);
227
+
228
+ // If panel is collapsed, uncollapse it
229
+ if( $clicked.hasClass(settings.collapsedClass) ){
230
+
231
+ // If ajax panel and not already cached
232
+ if( ajaxUrl && (!settings.cache || !$clicked.parent().data('easytabs').cached) ) {
233
+ $container.trigger('easytabs:ajax:beforeSend', [$clicked, $targetPanel]);
234
+
235
+ $targetPanel.load(ajaxUrl, function(response, status, xhr){
236
+ $clicked.parent().data('easytabs').cached = true;
237
+ $container.trigger('easytabs:ajax:complete', [$clicked, $targetPanel, response, status, xhr]);
238
+ });
239
+ }
240
+
241
+ // Update CSS classes of tab and panel
242
+ $clicked.parent()
243
+ .removeClass(settings.collapsedClass)
244
+ .addClass(settings.tabActiveClass)
245
+ .children()
246
+ .removeClass(settings.collapsedClass)
247
+ .addClass(settings.tabActiveClass);
248
+
249
+ $targetPanel
250
+ .addClass(settings.panelActiveClass)
251
+ [transitions.uncollapse](transitions.speed, settings.transitionUncollapseEasing, function(){
252
+ $container.trigger('easytabs:midTransition', [$clicked, $targetPanel, settings]);
253
+ if(typeof callback == 'function') callback();
254
+ });
255
+
256
+ // Otherwise, collapse it
257
+ } else {
258
+
259
+ // Update CSS classes of tab and panel
260
+ $clicked.addClass(settings.collapsedClass)
261
+ .parent()
262
+ .addClass(settings.collapsedClass);
263
+
264
+ $targetPanel
265
+ .removeClass(settings.panelActiveClass)
266
+ [transitions.collapse](transitions.speed, settings.transitionCollapseEasing, function(){
267
+ $container.trigger("easytabs:midTransition", [$clicked, $targetPanel, settings]);
268
+ if(typeof callback == 'function') callback();
269
+ });
270
+ }
271
+ }
272
+ };
273
+
274
+
275
+ // Find tab with target panel matching value
276
+ plugin.matchTab = function(hash) {
277
+ return plugin.tabs.find("[href='" + hash + "'],[data-target='" + hash + "']").first();
278
+ };
279
+
280
+ // Find panel with `id` matching value
281
+ plugin.matchInPanel = function(hash) {
282
+ return ( hash ? plugin.panels.filter(':has(' + hash + ')').first() : [] );
283
+ };
284
+
285
+ // Select matching tab when URL hash changes
286
+ plugin.selectTabFromHashChange = function() {
287
+ var hash = window.location.hash.match(/^[^\?]*/)[0],
288
+ $tab = plugin.matchTab(hash),
289
+ $panel;
290
+
291
+ if ( settings.updateHash ) {
292
+
293
+ // If hash directly matches tab
294
+ if( $tab.length ){
295
+ skipUpdateToHash = true;
296
+ plugin.selectTab( $tab );
297
+
298
+ } else {
299
+ $panel = plugin.matchInPanel(hash);
300
+
301
+ // If panel contains element matching hash
302
+ if ( $panel.length ) {
303
+ hash = '#' + $panel.attr('id');
304
+ $tab = plugin.matchTab(hash);
305
+ skipUpdateToHash = true;
306
+ plugin.selectTab( $tab );
307
+
308
+ // If default tab is not active...
309
+ } else if ( ! $defaultTab.hasClass(settings.tabActiveClass) && ! settings.cycle ) {
310
+
311
+ // ...and hash is blank or matches a parent of the tab container or
312
+ // if the last tab (before the hash updated) was one of the other tabs in this container.
313
+ if ( hash === '' || plugin.matchTab(lastHash).length || $container.closest(hash).length ) {
314
+ skipUpdateToHash = true;
315
+ plugin.selectTab( $defaultTabLink );
316
+ }
317
+ }
318
+ }
319
+ }
320
+ };
321
+
322
+ // Cycle through tabs
323
+ plugin.cycleTabs = function(tabNumber){
324
+ if(settings.cycle){
325
+ tabNumber = tabNumber % plugin.tabs.length;
326
+ $tab = $( plugin.tabs[tabNumber] ).children("a").first();
327
+ skipUpdateToHash = true;
328
+ plugin.selectTab( $tab, function() {
329
+ setTimeout(function(){ plugin.cycleTabs(tabNumber + 1); }, settings.cycle);
330
+ });
331
+ }
332
+ };
333
+
334
+ // Convenient public methods
335
+ plugin.publicMethods = {
336
+ select: function(tabSelector){
337
+ var $tab;
338
+
339
+ // Find tab container that matches selector (like 'li#tab-one' which contains tab link)
340
+ if ( ($tab = plugin.tabs.filter(tabSelector)).length === 0 ) {
341
+
342
+ // Find direct tab link that matches href (like 'a[href="#panel-1"]')
343
+ if ( ($tab = plugin.tabs.find("a[href='" + tabSelector + "']")).length === 0 ) {
344
+
345
+ // Find direct tab link that matches selector (like 'a#tab-1')
346
+ if ( ($tab = plugin.tabs.find("a" + tabSelector)).length === 0 ) {
347
+
348
+ // Find direct tab link that matches data-target (like 'a[data-target="#panel-1"]')
349
+ if ( ($tab = plugin.tabs.find("[data-target='" + tabSelector + "']")).length === 0 ) {
350
+
351
+ // Find direct tab link that ends in the matching href (like 'a[href$="#panel-1"]', which would also match http://example.com/currentpage/#panel-1)
352
+ if ( ($tab = plugin.tabs.find("a[href$='" + tabSelector + "']")).length === 0 ) {
353
+
354
+ $.error('Tab \'' + tabSelector + '\' does not exist in tab set');
355
+ }
356
+ }
357
+ }
358
+ }
359
+ } else {
360
+ // Select the child tab link, since the first option finds the tab container (like <li>)
361
+ $tab = $tab.children("a").first();
362
+ }
363
+ plugin.selectTab($tab);
364
+ }
365
+ };
366
+
367
+ // =============================================================
368
+ // Private functions
369
+ // =============================================================
370
+
371
+ // Triggers an event on an element and returns the event result
372
+ var fire = function(obj, name, data) {
373
+ var event = $.Event(name);
374
+ obj.trigger(event, data);
375
+ return event.result !== false;
376
+ }
377
+
378
+ // Add CSS classes to markup (if specified), called by init
379
+ var addClasses = function() {
380
+ $container.addClass(settings.containerClass);
381
+ plugin.tabs.parent().addClass(settings.tabsClass);
382
+ plugin.tabs.addClass(settings.tabClass);
383
+ plugin.panels.addClass(settings.panelClass);
384
+ };
385
+
386
+ // Set the default tab, whether from hash (bookmarked) or option,
387
+ // called by init
388
+ var setDefaultTab = function(){
389
+ var hash = window.location.hash.match(/^[^\?]*/)[0],
390
+ $selectedTab = plugin.matchTab(hash).parent(),
391
+ $panel;
392
+
393
+ // If hash directly matches one of the tabs, active on page-load
394
+ if( $selectedTab.length === 1 ){
395
+ $defaultTab = $selectedTab;
396
+ settings.cycle = false;
397
+
398
+ } else {
399
+ $panel = plugin.matchInPanel(hash);
400
+
401
+ // If one of the panels contains the element matching the hash,
402
+ // make it active on page-load
403
+ if ( $panel.length ) {
404
+ hash = '#' + $panel.attr('id');
405
+ $defaultTab = plugin.matchTab(hash).parent();
406
+
407
+ // Otherwise, make the default tab the one that's active on page-load
408
+ } else {
409
+ $defaultTab = plugin.tabs.parent().find(settings.defaultTab);
410
+ if ( $defaultTab.length === 0 ) {
411
+ $.error("The specified default tab ('" + settings.defaultTab + "') could not be found in the tab set.");
412
+ }
413
+ }
414
+ }
415
+
416
+ $defaultTabLink = $defaultTab.children("a").first();
417
+
418
+ activateDefaultTab($selectedTab);
419
+ };
420
+
421
+ // Activate defaultTab (or collapse by default), called by setDefaultTab
422
+ var activateDefaultTab = function($selectedTab) {
423
+ var defaultPanel,
424
+ defaultAjaxUrl;
425
+
426
+ if ( settings.collapsible && $selectedTab.length === 0 && settings.collapsedByDefault ) {
427
+ $defaultTab
428
+ .addClass(settings.collapsedClass)
429
+ .children()
430
+ .addClass(settings.collapsedClass);
431
+
432
+ } else {
433
+
434
+ defaultPanel = $( $defaultTab.data('easytabs').panel );
435
+ defaultAjaxUrl = $defaultTab.data('easytabs').ajax;
436
+
437
+ if ( defaultAjaxUrl && (!settings.cache || !$defaultTab.data('easytabs').cached) ) {
438
+ $container.trigger('easytabs:ajax:beforeSend', [$defaultTabLink, defaultPanel]);
439
+ defaultPanel.load(defaultAjaxUrl, function(response, status, xhr){
440
+ $defaultTab.data('easytabs').cached = true;
441
+ $container.trigger('easytabs:ajax:complete', [$defaultTabLink, defaultPanel, response, status, xhr]);
442
+ });
443
+ }
444
+
445
+ $defaultTab.data('easytabs').panel
446
+ .show()
447
+ .addClass(settings.panelActiveClass);
448
+
449
+ $defaultTab
450
+ .addClass(settings.tabActiveClass)
451
+ .children()
452
+ .addClass(settings.tabActiveClass);
453
+ }
454
+ };
455
+
456
+ // Bind tab-select funtionality to namespaced click event, called by
457
+ // init
458
+ var bindToTabClicks = function() {
459
+ plugin.tabs.children("a").bind("click.easytabs", function(e) {
460
+
461
+ // Stop cycling when a tab is clicked
462
+ settings.cycle = false;
463
+
464
+ // Hash will be updated when tab is clicked,
465
+ // don't cause tab to re-select when hash-change event is fired
466
+ skipUpdateToHash = false;
467
+
468
+ // Select the panel for the clicked tab
469
+ plugin.selectTab( $(this) );
470
+
471
+ // Don't follow the link to the anchor
472
+ e.preventDefault();
473
+ });
474
+ };
475
+
476
+ // Activate a given tab/panel, called from plugin.selectTab:
477
+ //
478
+ // * fire `easytabs:before` hook
479
+ // * get ajax if new tab is an uncached ajax tab
480
+ // * animate out previously-active panel
481
+ // * fire `easytabs:midTransition` hook
482
+ // * update URL hash
483
+ // * animate in newly-active panel
484
+ // * update CSS classes for inactive and active tabs/panels
485
+ //
486
+ // TODO: This could probably be broken out into many more modular
487
+ // functions
488
+ var activateTab = function($clicked, $targetPanel, ajaxUrl, callback) {
489
+ plugin.panels.stop(true,true);
490
+
491
+ if( fire($container,"easytabs:before", [$clicked, $targetPanel, settings]) ){
492
+ var $visiblePanel = plugin.panels.filter(":visible"),
493
+ $panelContainer = $targetPanel.parent(),
494
+ targetHeight,
495
+ visibleHeight,
496
+ heightDifference,
497
+ showPanel,
498
+ hash = window.location.hash.match(/^[^\?]*/)[0];
499
+
500
+ if (settings.animate) {
501
+ targetHeight = getHeightForHidden($targetPanel);
502
+ visibleHeight = $visiblePanel.length ? setAndReturnHeight($visiblePanel) : 0;
503
+ heightDifference = targetHeight - visibleHeight;
504
+ }
505
+
506
+ // Set lastHash to help indicate if defaultTab should be
507
+ // activated across multiple tab instances.
508
+ lastHash = hash;
509
+
510
+ // TODO: Move this function elsewhere
511
+ showPanel = function() {
512
+ // At this point, the previous panel is hidden, and the new one will be selected
513
+ $container.trigger("easytabs:midTransition", [$clicked, $targetPanel, settings]);
514
+
515
+ // Gracefully animate between panels of differing heights, start height change animation *after* panel change if panel needs to contract,
516
+ // so that there is no chance of making the visible panel overflowing the height of the target panel
517
+ if (settings.animate && settings.transitionIn == 'fadeIn') {
518
+ if (heightDifference < 0)
519
+ $panelContainer.animate({
520
+ height: $panelContainer.height() + heightDifference
521
+ }, transitions.halfSpeed ).css({ 'min-height': '' });
522
+ }
523
+
524
+ if ( settings.updateHash && ! skipUpdateToHash ) {
525
+ //window.location = url.toString().replace((url.pathname + hash), (url.pathname + $clicked.attr("href")));
526
+ // Not sure why this behaves so differently, but it's more straight forward and seems to have less side-effects
527
+ window.location.hash = '#' + $targetPanel.attr('id');
528
+ } else {
529
+ skipUpdateToHash = false;
530
+ }
531
+
532
+ $targetPanel
533
+ [transitions.show](transitions.speed, settings.transitionInEasing, function(){
534
+ $panelContainer.css({height: '', 'min-height': ''}); // After the transition, unset the height
535
+ $container.trigger("easytabs:after", [$clicked, $targetPanel, settings]);
536
+ // callback only gets called if selectTab actually does something, since it's inside the if block
537
+ if(typeof callback == 'function'){
538
+ callback();
539
+ }
540
+ });
541
+ };
542
+
543
+ if ( ajaxUrl && (!settings.cache || !$clicked.parent().data('easytabs').cached) ) {
544
+ $container.trigger('easytabs:ajax:beforeSend', [$clicked, $targetPanel]);
545
+ $targetPanel.load(ajaxUrl, function(response, status, xhr){
546
+ $clicked.parent().data('easytabs').cached = true;
547
+ $container.trigger('easytabs:ajax:complete', [$clicked, $targetPanel, response, status, xhr]);
548
+ });
549
+ }
550
+
551
+ // Gracefully animate between panels of differing heights, start height change animation *before* panel change if panel needs to expand,
552
+ // so that there is no chance of making the target panel overflowing the height of the visible panel
553
+ if( settings.animate && settings.transitionOut == 'fadeOut' ) {
554
+ if( heightDifference > 0 ) {
555
+ $panelContainer.animate({
556
+ height: ( $panelContainer.height() + heightDifference )
557
+ }, transitions.halfSpeed );
558
+ } else {
559
+ // Prevent height jumping before height transition is triggered at midTransition
560
+ $panelContainer.css({ 'min-height': $panelContainer.height() });
561
+ }
562
+ }
563
+
564
+ // Change the active tab *first* to provide immediate feedback when the user clicks
565
+ plugin.tabs.filter("." + settings.tabActiveClass).removeClass(settings.tabActiveClass).children().removeClass(settings.tabActiveClass);
566
+ plugin.tabs.filter("." + settings.collapsedClass).removeClass(settings.collapsedClass).children().removeClass(settings.collapsedClass);
567
+ $clicked.parent().addClass(settings.tabActiveClass).children().addClass(settings.tabActiveClass);
568
+
569
+ plugin.panels.filter("." + settings.panelActiveClass).removeClass(settings.panelActiveClass);
570
+ $targetPanel.addClass(settings.panelActiveClass);
571
+
572
+ if( $visiblePanel.length ) {
573
+ $visiblePanel
574
+ [transitions.hide](transitions.speed, settings.transitionOutEasing, showPanel);
575
+ } else {
576
+ $targetPanel
577
+ [transitions.uncollapse](transitions.speed, settings.transitionUncollapseEasing, showPanel);
578
+ }
579
+ }
580
+ };
581
+
582
+ // Get heights of panels to enable animation between panels of
583
+ // differing heights, called by activateTab
584
+ var getHeightForHidden = function($targetPanel){
585
+
586
+ if ( $targetPanel.data('easytabs') && $targetPanel.data('easytabs').lastHeight ) {
587
+ return $targetPanel.data('easytabs').lastHeight;
588
+ }
589
+
590
+ // this is the only property easytabs changes, so we need to grab its value on each tab change
591
+ var display = $targetPanel.css('display'),
592
+
593
+ // Workaround, because firefox returns wrong height if element itself has absolute positioning
594
+ height = $targetPanel
595
+ .wrap($('<div>', {position: 'absolute', 'visibility': 'hidden', 'overflow': 'hidden'}))
596
+ .css({'position':'relative','visibility':'hidden','display':'block'})
597
+ .outerHeight();
598
+
599
+ $targetPanel.unwrap();
600
+
601
+ // Return element to previous state
602
+ $targetPanel.css({
603
+ position: $targetPanel.data('easytabs').position,
604
+ visibility: $targetPanel.data('easytabs').visibility,
605
+ display: display
606
+ });
607
+
608
+ // Cache height
609
+ $targetPanel.data('easytabs').lastHeight = height;
610
+
611
+ return height;
612
+ };
613
+
614
+ // Since the height of the visible panel may have been manipulated due to interaction,
615
+ // we want to re-cache the visible height on each tab change, called
616
+ // by activateTab
617
+ var setAndReturnHeight = function($visiblePanel) {
618
+ var height = $visiblePanel.outerHeight();
619
+
620
+ if( $visiblePanel.data('easytabs') ) {
621
+ $visiblePanel.data('easytabs').lastHeight = height;
622
+ } else {
623
+ $visiblePanel.data('easytabs', {lastHeight: height});
624
+ }
625
+ return height;
626
+ };
627
+
628
+ // Setup hash-change callback for forward- and back-button
629
+ // functionality, called by init
630
+ var initHashChange = function(){
631
+
632
+ // enabling back-button with jquery.hashchange plugin
633
+ // http://benalman.com/projects/jquery-hashchange-plugin/
634
+ if(typeof $(window).hashchange === 'function'){
635
+ $(window).hashchange( function(){
636
+ plugin.selectTabFromHashChange();
637
+ });
638
+ } else if ($.address && typeof $.address.change === 'function') { // back-button with jquery.address plugin http://www.asual.com/jquery/address/docs/
639
+ $.address.change( function(){
640
+ plugin.selectTabFromHashChange();
641
+ });
642
+ }
643
+ };
644
+
645
+ // Begin cycling if set in options, called by init
646
+ var initCycle = function(){
647
+ var tabNumber;
648
+ if (settings.cycle) {
649
+ tabNumber = plugin.tabs.index($defaultTab);
650
+ setTimeout( function(){ plugin.cycleTabs(tabNumber + 1); }, settings.cycle);
651
+ }
652
+ };
653
+
654
+
655
+ plugin.init();
656
+
657
+ };
658
+
659
+ $.fn.easytabs = function(options) {
660
+ var args = arguments;
661
+
662
+ return this.each(function() {
663
+ var $this = $(this),
664
+ plugin = $this.data('easytabs');
665
+
666
+ // Initialization was called with $(el).easytabs( { options } );
667
+ if (undefined === plugin) {
668
+ plugin = new $.easytabs(this, options);
669
+ $this.data('easytabs', plugin);
670
+ }
671
+
672
+ // User called public method
673
+ if ( plugin.publicMethods[options] ){
674
+ return plugin.publicMethods[options](Array.prototype.slice.call( args, 1 ));
675
+ }
676
+ });
677
+ };
678
+
679
+ })(jQuery);