diff options
Diffstat (limited to 'sites/all/modules/ctools/js')
-rw-r--r-- | sites/all/modules/ctools/js/ajax-responder.js | 126 | ||||
-rw-r--r-- | sites/all/modules/ctools/js/auto-submit.js | 100 | ||||
-rw-r--r-- | sites/all/modules/ctools/js/collapsible-div.js | 241 | ||||
-rw-r--r-- | sites/all/modules/ctools/js/dependent.js | 231 | ||||
-rw-r--r-- | sites/all/modules/ctools/js/dropbutton.js | 94 | ||||
-rw-r--r-- | sites/all/modules/ctools/js/dropdown.js | 87 | ||||
-rw-r--r-- | sites/all/modules/ctools/js/jump-menu.js | 42 | ||||
-rw-r--r-- | sites/all/modules/ctools/js/modal.js | 696 | ||||
-rw-r--r-- | sites/all/modules/ctools/js/states-show.js | 43 | ||||
-rw-r--r-- | sites/all/modules/ctools/js/stylizer.js | 220 |
10 files changed, 1880 insertions, 0 deletions
diff --git a/sites/all/modules/ctools/js/ajax-responder.js b/sites/all/modules/ctools/js/ajax-responder.js new file mode 100644 index 000000000..1cad618ef --- /dev/null +++ b/sites/all/modules/ctools/js/ajax-responder.js @@ -0,0 +1,126 @@ +/** + * @file + * + * CTools flexible AJAX responder object. + */ + +(function ($) { + Drupal.CTools = Drupal.CTools || {}; + Drupal.CTools.AJAX = Drupal.CTools.AJAX || {}; + /** + * Grab the response from the server and store it. + * + * @todo restore the warm cache functionality + */ + Drupal.CTools.AJAX.warmCache = function () { + // Store this expression for a minor speed improvement. + $this = $(this); + var old_url = $this.attr('href'); + // If we are currently fetching, or if we have fetched this already which is + // ideal for things like pagers, where the previous page might already have + // been seen in the cache. + if ($this.hasClass('ctools-fetching') || Drupal.CTools.AJAX.commandCache[old_url]) { + return false; + } + + // Grab all the links that match this url and add the fetching class. + // This allows the caching system to grab each url once and only once + // instead of grabbing the url once per <a>. + var $objects = $('a[href="' + old_url + '"]') + $objects.addClass('ctools-fetching'); + try { + url = old_url.replace(/\/nojs(\/|$)/g, '/ajax$1'); + $.ajax({ + type: "POST", + url: url, + data: { 'js': 1, 'ctools_ajax': 1}, + global: true, + success: function (data) { + Drupal.CTools.AJAX.commandCache[old_url] = data; + $objects.addClass('ctools-cache-warmed').trigger('ctools-cache-warm', [data]); + }, + complete: function() { + $objects.removeClass('ctools-fetching'); + }, + dataType: 'json' + }); + } + catch (err) { + $objects.removeClass('ctools-fetching'); + return false; + } + + return false; + }; + + /** + * Cachable click handler to fetch the commands out of the cache or from url. + */ + Drupal.CTools.AJAX.clickAJAXCacheLink = function () { + $this = $(this); + if ($this.hasClass('ctools-fetching')) { + $this.bind('ctools-cache-warm', function (event, data) { + Drupal.CTools.AJAX.respond(data); + }); + return false; + } + else { + if ($this.hasClass('ctools-cache-warmed') && Drupal.CTools.AJAX.commandCache[$this.attr('href')]) { + Drupal.CTools.AJAX.respond(Drupal.CTools.AJAX.commandCache[$this.attr('href')]); + return false; + } + else { + return Drupal.CTools.AJAX.clickAJAXLink.apply(this); + } + } + }; + + /** + * Find a URL for an AJAX button. + * + * The URL for this gadget will be composed of the values of items by + * taking the ID of this item and adding -url and looking for that + * class. They need to be in the form in order since we will + * concat them all together using '/'. + */ + Drupal.CTools.AJAX.findURL = function(item) { + var url = ''; + var url_class = '.' + $(item).attr('id') + '-url'; + $(url_class).each( + function() { + var $this = $(this); + if (url && $this.val()) { + url += '/'; + } + url += $this.val(); + }); + return url; + }; + + // Hide these in a ready to ensure that Drupal.ajax is set up first. + $(function() { + Drupal.ajax.prototype.commands.attr = function(ajax, data, status) { + $(data.selector).attr(data.name, data.value); + }; + + + Drupal.ajax.prototype.commands.redirect = function(ajax, data, status) { + if (data.delay > 0) { + setTimeout(function () { + location.href = data.url; + }, data.delay); + } + else { + location.href = data.url; + } + }; + + Drupal.ajax.prototype.commands.reload = function(ajax, data, status) { + location.reload(); + }; + + Drupal.ajax.prototype.commands.submit = function(ajax, data, status) { + $(data.selector).submit(); + } + }); +})(jQuery); diff --git a/sites/all/modules/ctools/js/auto-submit.js b/sites/all/modules/ctools/js/auto-submit.js new file mode 100644 index 000000000..a3e9aa42a --- /dev/null +++ b/sites/all/modules/ctools/js/auto-submit.js @@ -0,0 +1,100 @@ +(function($){ +/** + * To make a form auto submit, all you have to do is 3 things: + * + * ctools_add_js('auto-submit'); + * + * On gadgets you want to auto-submit when changed, add the ctools-auto-submit + * class. With FAPI, add: + * @code + * '#attributes' => array('class' => array('ctools-auto-submit')), + * @endcode + * + * If you want to have auto-submit for every form element, + * add the ctools-auto-submit-full-form to the form. With FAPI, add: + * @code + * '#attributes' => array('class' => array('ctools-auto-submit-full-form')), + * @endcode + * + * If you want to exclude a field from the ctool-auto-submit-full-form auto submission, + * add the class ctools-auto-submit-exclude to the form element. With FAPI, add: + * @code + * '#attributes' => array('class' => array('ctools-auto-submit-exclude')), + * @endcode + * + * Finally, you have to identify which button you want clicked for autosubmit. + * The behavior of this button will be honored if it's ajaxy or not: + * @code + * '#attributes' => array('class' => array('ctools-use-ajax', 'ctools-auto-submit-click')), + * @endcode + * + * Currently only 'select', 'radio', 'checkbox' and 'textfield' types are supported. We probably + * could use additional support for HTML5 input types. + */ + +Drupal.behaviors.CToolsAutoSubmit = { + attach: function(context) { + // 'this' references the form element + function triggerSubmit (e) { + var $this = $(this); + if (!$this.hasClass('ctools-ajaxing')) { + $this.find('.ctools-auto-submit-click').click(); + } + } + + // the change event bubbles so we only need to bind it to the outer form + $('form.ctools-auto-submit-full-form', context) + .add('.ctools-auto-submit', context) + .filter('form, select, input:not(:text, :submit)') + .once('ctools-auto-submit') + .change(function (e) { + // don't trigger on text change for full-form + if ($(e.target).is(':not(:text, :submit, .ctools-auto-submit-exclude)')) { + triggerSubmit.call(e.target.form); + } + }); + + // e.keyCode: key + var discardKeyCode = [ + 16, // shift + 17, // ctrl + 18, // alt + 20, // caps lock + 33, // page up + 34, // page down + 35, // end + 36, // home + 37, // left arrow + 38, // up arrow + 39, // right arrow + 40, // down arrow + 9, // tab + 13, // enter + 27 // esc + ]; + // Don't wait for change event on textfields + $('.ctools-auto-submit-full-form input:text, input:text.ctools-auto-submit', context) + .filter(':not(.ctools-auto-submit-exclude)') + .once('ctools-auto-submit', function () { + // each textinput element has his own timeout + var timeoutID = 0; + $(this) + .bind('keydown keyup', function (e) { + if ($.inArray(e.keyCode, discardKeyCode) === -1) { + timeoutID && clearTimeout(timeoutID); + } + }) + .keyup(function(e) { + if ($.inArray(e.keyCode, discardKeyCode) === -1) { + timeoutID = setTimeout($.proxy(triggerSubmit, this.form), 500); + } + }) + .bind('change', function (e) { + if ($.inArray(e.keyCode, discardKeyCode) === -1) { + timeoutID = setTimeout($.proxy(triggerSubmit, this.form), 500); + } + }); + }); + } +} +})(jQuery); diff --git a/sites/all/modules/ctools/js/collapsible-div.js b/sites/all/modules/ctools/js/collapsible-div.js new file mode 100644 index 000000000..134151c3d --- /dev/null +++ b/sites/all/modules/ctools/js/collapsible-div.js @@ -0,0 +1,241 @@ +/** + * @file + * Javascript required for a simple collapsible div. + * + * Creating a collapsible div with this doesn't take too much. There are + * three classes necessary: + * + * - ctools-collapsible-container: This is the overall container that will be + * collapsible. This must be a div. + * - ctools-collapsible-handle: This is the title area, and is what will be + * visible when it is collapsed. This can be any block element, such as div + * or h2. + * - ctools-collapsible-content: This is the ocntent area and will only be + * visible when expanded. This must be a div. + * + * Adding 'ctools-collapsible-remember' to the container class will cause the + * state of the container to be stored in a cookie, and remembered from page + * load to page load. This will only work if the container has a unique ID, so + * very carefully add IDs to your containers. + * + * If the class 'ctools-no-container' is placed on the container, the container + * will be the handle. The content will be found by appending '-content' to the + * id of the handle. The ctools-collapsible-handle and + * ctools-collapsible-content classes will not be required in that case, and no + * restrictions on what of data the container is are placed. Like + * ctools-collapsible-remember this requires an id to eist. + * + * The content will be 'open' unless the container class has 'ctools-collapsed' + * as a class, which will cause the container to draw collapsed. + */ + +(function ($) { + // All CTools tools begin with this if they need to use the CTools namespace. + if (!Drupal.CTools) { + Drupal.CTools = {}; + } + + /** + * Object to store state. + * + * This object will remember the state of collapsible containers. The first + * time a state is requested, it will check the cookie and set up the variable. + * If a state has been changed, when the window is unloaded the state will be + * saved. + */ + Drupal.CTools.Collapsible = { + state: {}, + stateLoaded: false, + stateChanged: false, + cookieString: 'ctools-collapsible-state=', + + /** + * Get the current collapsed state of a container. + * + * If set to 1, the container is open. If set to -1, the container is + * collapsed. If unset the state is unknown, and the default state should + * be used. + */ + getState: function (id) { + if (!this.stateLoaded) { + this.loadCookie(); + } + + return this.state[id]; + }, + + /** + * Set the collapsed state of a container for subsequent page loads. + * + * Set the state to 1 for open, -1 for collapsed. + */ + setState: function (id, state) { + if (!this.stateLoaded) { + this.loadCookie(); + } + + this.state[id] = state; + + if (!this.stateChanged) { + this.stateChanged = true; + $(window).unload(this.unload); + } + }, + + /** + * Check the cookie and load the state variable. + */ + loadCookie: function () { + // If there is a previous instance of this cookie + if (document.cookie.length > 0) { + // Get the number of characters that have the list of values + // from our string index. + offset = document.cookie.indexOf(this.cookieString); + + // If its positive, there is a list! + if (offset != -1) { + offset += this.cookieString.length; + var end = document.cookie.indexOf(';', offset); + if (end == -1) { + end = document.cookie.length; + } + + // Get a list of all values that are saved on our string + var cookie = unescape(document.cookie.substring(offset, end)); + + if (cookie != '') { + var cookieList = cookie.split(','); + for (var i = 0; i < cookieList.length; i++) { + var info = cookieList[i].split(':'); + this.state[info[0]] = info[1]; + } + } + } + } + + this.stateLoaded = true; + }, + + /** + * Turn the state variable into a string and store it in the cookie. + */ + storeCookie: function () { + var cookie = ''; + + // Get a list of IDs, saparated by comma + for (i in this.state) { + if (cookie != '') { + cookie += ','; + } + cookie += i + ':' + this.state[i]; + } + + // Save this values on the cookie + document.cookie = this.cookieString + escape(cookie) + ';path=/'; + }, + + /** + * Respond to the unload event by storing the current state. + */ + unload: function() { + Drupal.CTools.Collapsible.storeCookie(); + } + }; + + // Set up an array for callbacks. + Drupal.CTools.CollapsibleCallbacks = []; + Drupal.CTools.CollapsibleCallbacksAfterToggle = []; + + /** + * Bind collapsible behavior to a given container. + */ + Drupal.CTools.bindCollapsible = function () { + var $container = $(this); + + // Allow the specification of the 'no container' class, which means the + // handle and the container can be completely independent. + if ($container.hasClass('ctools-no-container') && $container.attr('id')) { + // In this case, the container *is* the handle and the content is found + // by adding '-content' to the id. Obviously, an id is required. + var handle = $container; + var content = $('#' + $container.attr('id') + '-content'); + } + else { + var handle = $container.children('.ctools-collapsible-handle'); + var content = $container.children('div.ctools-collapsible-content'); + } + + if (content.length) { + // Create the toggle item and place it in front of the toggle. + var toggle = $('<span class="ctools-toggle"></span>'); + handle.before(toggle); + + // If the remember class is set, check to see if we have a remembered + // state stored. + if ($container.hasClass('ctools-collapsible-remember') && $container.attr('id')) { + var state = Drupal.CTools.Collapsible.getState($container.attr('id')); + if (state == 1) { + $container.removeClass('ctools-collapsed'); + } + else if (state == -1) { + $container.addClass('ctools-collapsed'); + } + } + + // If we should start collapsed, do so: + if ($container.hasClass('ctools-collapsed')) { + toggle.toggleClass('ctools-toggle-collapsed'); + content.hide(); + } + + var afterToggle = function () { + if (Drupal.CTools.CollapsibleCallbacksAfterToggle) { + for (i in Drupal.CTools.CollapsibleCallbacksAfterToggle) { + Drupal.CTools.CollapsibleCallbacksAfterToggle[i]($container, handle, content, toggle); + } + } + } + + var clickMe = function () { + if (Drupal.CTools.CollapsibleCallbacks) { + for (i in Drupal.CTools.CollapsibleCallbacks) { + Drupal.CTools.CollapsibleCallbacks[i]($container, handle, content, toggle); + } + } + + // If the container is a table element slideToggle does not do what + // we want, so use toggle() instead. + if ($container.is('table')) { + content.toggle(0, afterToggle); + } + else { + content.slideToggle(100, afterToggle); + } + + $container.toggleClass('ctools-collapsed'); + toggle.toggleClass('ctools-toggle-collapsed'); + + // If we're supposed to remember the state of this class, do so. + if ($container.hasClass('ctools-collapsible-remember') && $container.attr('id')) { + var state = toggle.hasClass('ctools-toggle-collapsed') ? -1 : 1; + Drupal.CTools.Collapsible.setState($container.attr('id'), state); + } + + return false; + } + + // Let both the toggle and the handle be clickable. + toggle.click(clickMe); + handle.click(clickMe); + } + }; + + /** + * Support Drupal's 'behaviors' system for binding. + */ + Drupal.behaviors.CToolsCollapsible = { + attach: function(context) { + $('.ctools-collapsible-container', context).once('ctools-collapsible', Drupal.CTools.bindCollapsible); + } + } +})(jQuery); diff --git a/sites/all/modules/ctools/js/dependent.js b/sites/all/modules/ctools/js/dependent.js new file mode 100644 index 000000000..f74ec81ba --- /dev/null +++ b/sites/all/modules/ctools/js/dependent.js @@ -0,0 +1,231 @@ +/** + * @file + * Provides dependent visibility for form items in CTools' ajax forms. + * + * To your $form item definition add: + * - '#process' => array('ctools_process_dependency'), + * - '#dependency' => array('id-of-form-item' => array(list, of, values, that, + * make, this, item, show), + * + * Special considerations: + * - Radios are harder. Because Drupal doesn't give radio groups individual IDs, + * use 'radio:name-of-radio'. + * + * - Checkboxes don't have their own id, so you need to add one in a div + * around the checkboxes via #prefix and #suffix. You actually need to add TWO + * divs because it's the parent that gets hidden. Also be sure to retain the + * 'expand_checkboxes' in the #process array, because the CTools process will + * override it. + */ + +(function ($) { + Drupal.CTools = Drupal.CTools || {}; + Drupal.CTools.dependent = {}; + + Drupal.CTools.dependent.bindings = {}; + Drupal.CTools.dependent.activeBindings = {}; + Drupal.CTools.dependent.activeTriggers = []; + + Drupal.CTools.dependent.inArray = function(array, search_term) { + var i = array.length; + while (i--) { + if (array[i] == search_term) { + return true; + } + } + return false; + } + + + Drupal.CTools.dependent.autoAttach = function() { + // Clear active bindings and triggers. + for (i in Drupal.CTools.dependent.activeTriggers) { + $(Drupal.CTools.dependent.activeTriggers[i]).unbind('change.ctools-dependent'); + } + Drupal.CTools.dependent.activeTriggers = []; + Drupal.CTools.dependent.activeBindings = {}; + Drupal.CTools.dependent.bindings = {}; + + if (!Drupal.settings.CTools) { + return; + } + + // Iterate through all relationships + for (id in Drupal.settings.CTools.dependent) { + // Test to make sure the id even exists; this helps clean up multiple + // AJAX calls with multiple forms. + + // Drupal.CTools.dependent.activeBindings[id] is a boolean, + // whether the binding is active or not. Defaults to no. + Drupal.CTools.dependent.activeBindings[id] = 0; + // Iterate through all possible values + for(bind_id in Drupal.settings.CTools.dependent[id].values) { + // This creates a backward relationship. The bind_id is the ID + // of the element which needs to change in order for the id to hide or become shown. + // The id is the ID of the item which will be conditionally hidden or shown. + // Here we're setting the bindings for the bind + // id to be an empty array if it doesn't already have bindings to it + if (!Drupal.CTools.dependent.bindings[bind_id]) { + Drupal.CTools.dependent.bindings[bind_id] = []; + } + // Add this ID + Drupal.CTools.dependent.bindings[bind_id].push(id); + // Big long if statement. + // Drupal.settings.CTools.dependent[id].values[bind_id] holds the possible values + + if (bind_id.substring(0, 6) == 'radio:') { + var trigger_id = "input[name='" + bind_id.substring(6) + "']"; + } + else { + var trigger_id = '#' + bind_id; + } + + Drupal.CTools.dependent.activeTriggers.push(trigger_id); + + if ($(trigger_id).attr('type') == 'checkbox') { + $(trigger_id).siblings('label').addClass('hidden-options'); + } + + var getValue = function(item, trigger) { + if ($(trigger).size() == 0) { + return null; + } + + if (item.substring(0, 6) == 'radio:') { + var val = $(trigger + ':checked').val(); + } + else { + switch ($(trigger).attr('type')) { + case 'checkbox': + var val = $(trigger).attr('checked') ? true : false; + + if (val) { + $(trigger).siblings('label').removeClass('hidden-options').addClass('expanded-options'); + } + else { + $(trigger).siblings('label').removeClass('expanded-options').addClass('hidden-options'); + } + + break; + default: + var val = $(trigger).val(); + } + } + return val; + } + + var setChangeTrigger = function(trigger_id, bind_id) { + // Triggered when change() is clicked. + var changeTrigger = function() { + var val = getValue(bind_id, trigger_id); + + if (val == null) { + return; + } + + for (i in Drupal.CTools.dependent.bindings[bind_id]) { + var id = Drupal.CTools.dependent.bindings[bind_id][i]; + // Fix numerous errors + if (typeof id != 'string') { + continue; + } + + // This bit had to be rewritten a bit because two properties on the + // same set caused the counter to go up and up and up. + if (!Drupal.CTools.dependent.activeBindings[id]) { + Drupal.CTools.dependent.activeBindings[id] = {}; + } + + if (val != null && Drupal.CTools.dependent.inArray(Drupal.settings.CTools.dependent[id].values[bind_id], val)) { + Drupal.CTools.dependent.activeBindings[id][bind_id] = 'bind'; + } + else { + delete Drupal.CTools.dependent.activeBindings[id][bind_id]; + } + + var len = 0; + for (i in Drupal.CTools.dependent.activeBindings[id]) { + len++; + } + + var object = $('#' + id + '-wrapper'); + if (!object.size()) { + // Some elements can't use the parent() method or they can + // damage things. They are guaranteed to have wrappers but + // only if dependent.inc provided them. This check prevents + // problems when multiple AJAX calls cause settings to build + // up. + var $original = $('#' + id); + if ($original.is('fieldset') || $original.is('textarea')) { + continue; + } + + object = $('#' + id).parent(); + } + + if (Drupal.settings.CTools.dependent[id].type == 'disable') { + if (Drupal.settings.CTools.dependent[id].num <= len) { + // Show if the element if criteria is matched + object.attr('disabled', false); + object.addClass('dependent-options'); + object.children().attr('disabled', false); + } + else { + // Otherwise hide. Use css rather than hide() because hide() + // does not work if the item is already hidden, for example, + // in a collapsed fieldset. + object.attr('disabled', true); + object.children().attr('disabled', true); + } + } + else { + if (Drupal.settings.CTools.dependent[id].num <= len) { + // Show if the element if criteria is matched + object.show(0); + object.addClass('dependent-options'); + } + else { + // Otherwise hide. Use css rather than hide() because hide() + // does not work if the item is already hidden, for example, + // in a collapsed fieldset. + object.css('display', 'none'); + } + } + } + } + + $(trigger_id).bind('change.ctools-dependent', function() { + // Trigger the internal change function + // the attr('id') is used because closures are more confusing + changeTrigger(trigger_id, bind_id); + }); + // Trigger initial reaction + changeTrigger(trigger_id, bind_id); + } + setChangeTrigger(trigger_id, bind_id); + } + } + } + + Drupal.behaviors.CToolsDependent = { + attach: function (context) { + Drupal.CTools.dependent.autoAttach(); + + // Really large sets of fields are too slow with the above method, so this + // is a sort of hacked one that's faster but much less flexible. + $("select.ctools-master-dependent") + .once('ctools-dependent') + .bind('change.ctools-dependent', function() { + var val = $(this).val(); + if (val == 'all') { + $('.ctools-dependent-all').show(0); + } + else { + $('.ctools-dependent-all').hide(0); + $('.ctools-dependent-' + val).show(0); + } + }) + .trigger('change.ctools-dependent'); + } + } +})(jQuery); diff --git a/sites/all/modules/ctools/js/dropbutton.js b/sites/all/modules/ctools/js/dropbutton.js new file mode 100644 index 000000000..f505550b6 --- /dev/null +++ b/sites/all/modules/ctools/js/dropbutton.js @@ -0,0 +1,94 @@ +/** + * @file + * Implement a simple, clickable dropbutton menu. + * + * See dropbutton.theme.inc for primary documentation. + * + * The javascript relies on four classes: + * - The dropbutton must be fully contained in a div with the class + * ctools-dropbutton. It must also contain the class ctools-no-js + * which will be immediately removed by the javascript; this allows for + * graceful degradation. + * - The trigger that opens the dropbutton must be an a tag wit hthe class + * ctools-dropbutton-link. The href should just be '#' as this will never + * be allowed to complete. + * - The part of the dropbutton that will appear when the link is clicked must + * be a div with class ctools-dropbutton-container. + * - Finally, ctools-dropbutton-hover will be placed on any link that is being + * hovered over, so that the browser can restyle the links. + * + * This tool isn't meant to replace click-tips or anything, it is specifically + * meant to work well presenting menus. + */ + +(function ($) { + Drupal.behaviors.CToolsDropbutton = { + attach: function() { + // Process buttons. All dropbuttons are buttons. + $('.ctools-button') + .once('ctools-button') + .removeClass('ctools-no-js'); + + // Process dropbuttons. Not all buttons are dropbuttons. + $('.ctools-dropbutton').once('ctools-dropbutton', function() { + var $dropbutton = $(this); + var $button = $('.ctools-content', $dropbutton); + var $secondaryActions = $('li', $button).not(':first'); + var $twisty = $(".ctools-link", $dropbutton); + var open = false; + var hovering = false; + var timerID = 0; + + var toggle = function(close) { + // if it's open or we're told to close it, close it. + if (open || close) { + // If we're just toggling it, close it immediately. + if (!close) { + open = false; + $secondaryActions.slideUp(100); + $dropbutton.removeClass('open'); + } + else { + // If we were told to close it, wait half a second to make + // sure that's what the user wanted. + // Clear any previous timer we were using. + if (timerID) { + clearTimeout(timerID); + } + timerID = setTimeout(function() { + if (!hovering) { + open = false; + $secondaryActions.slideUp(100); + $dropbutton.removeClass('open'); + }}, 500); + } + } + else { + // open it. + open = true; + $secondaryActions.animate({height: "show", opacity: "show"}, 100); + $dropbutton.addClass('open'); + } + } + // Hide the secondary actions initially. + $secondaryActions.hide(); + + $twisty.click(function() { + toggle(); + return false; + }); + + $dropbutton.hover( + function() { + hovering = true; + }, // hover in + function() { // hover out + hovering = false; + toggle(true); + return false; + } + ); + }); + } + } +})(jQuery); diff --git a/sites/all/modules/ctools/js/dropdown.js b/sites/all/modules/ctools/js/dropdown.js new file mode 100644 index 000000000..c829ae2fe --- /dev/null +++ b/sites/all/modules/ctools/js/dropdown.js @@ -0,0 +1,87 @@ +/** + * @file + * Implement a simple, clickable dropdown menu. + * + * See dropdown.theme.inc for primary documentation. + * + * The javascript relies on four classes: + * - The dropdown must be fully contained in a div with the class + * ctools-dropdown. It must also contain the class ctools-dropdown-no-js + * which will be immediately removed by the javascript; this allows for + * graceful degradation. + * - The trigger that opens the dropdown must be an a tag wit hthe class + * ctools-dropdown-link. The href should just be '#' as this will never + * be allowed to complete. + * - The part of the dropdown that will appear when the link is clicked must + * be a div with class ctools-dropdown-container. + * - Finally, ctools-dropdown-hover will be placed on any link that is being + * hovered over, so that the browser can restyle the links. + * + * This tool isn't meant to replace click-tips or anything, it is specifically + * meant to work well presenting menus. + */ + +(function ($) { + Drupal.behaviors.CToolsDropdown = { + attach: function() { + $('div.ctools-dropdown').once('ctools-dropdown', function() { + var $dropdown = $(this); + var open = false; + var hovering = false; + var timerID = 0; + + $dropdown.removeClass('ctools-dropdown-no-js'); + + var toggle = function(close) { + // if it's open or we're told to close it, close it. + if (open || close) { + // If we're just toggling it, close it immediately. + if (!close) { + open = false; + $("div.ctools-dropdown-container", $dropdown).slideUp(100); + } + else { + // If we were told to close it, wait half a second to make + // sure that's what the user wanted. + // Clear any previous timer we were using. + if (timerID) { + clearTimeout(timerID); + } + timerID = setTimeout(function() { + if (!hovering) { + open = false; + $("div.ctools-dropdown-container", $dropdown).slideUp(100); + } + }, 500); + } + } + else { + // open it. + open = true; + $("div.ctools-dropdown-container", $dropdown) + .animate({height: "show", opacity: "show"}, 100); + } + } + $("a.ctools-dropdown-link", $dropdown).click(function() { + toggle(); + return false; + }); + + $dropdown.hover( + function() { + hovering = true; + }, // hover in + function() { // hover out + hovering = false; + toggle(true); + return false; + }); + // @todo -- just use CSS for this noise. + $("div.ctools-dropdown-container a").hover( + function() { $(this).addClass('ctools-dropdown-hover'); }, + function() { $(this).removeClass('ctools-dropdown-hover'); } + ); + }); + } + } +})(jQuery); diff --git a/sites/all/modules/ctools/js/jump-menu.js b/sites/all/modules/ctools/js/jump-menu.js new file mode 100644 index 000000000..7b0928a68 --- /dev/null +++ b/sites/all/modules/ctools/js/jump-menu.js @@ -0,0 +1,42 @@ + +(function($) { + Drupal.behaviors.CToolsJumpMenu = { + attach: function(context) { + $('.ctools-jump-menu-hide') + .once('ctools-jump-menu') + .hide(); + + $('.ctools-jump-menu-change') + .once('ctools-jump-menu') + .change(function() { + var loc = $(this).val(); + var urlArray = loc.split('::'); + if (urlArray[1]) { + location.href = urlArray[1]; + } + else { + location.href = loc; + } + return false; + }); + + $('.ctools-jump-menu-button') + .once('ctools-jump-menu') + .click(function() { + // Instead of submitting the form, just perform the redirect. + + // Find our sibling value. + var $select = $(this).parents('form').find('.ctools-jump-menu-select'); + var loc = $select.val(); + var urlArray = loc.split('::'); + if (urlArray[1]) { + location.href = urlArray[1]; + } + else { + location.href = loc; + } + return false; + }); + } + } +})(jQuery); diff --git a/sites/all/modules/ctools/js/modal.js b/sites/all/modules/ctools/js/modal.js new file mode 100644 index 000000000..c757ef274 --- /dev/null +++ b/sites/all/modules/ctools/js/modal.js @@ -0,0 +1,696 @@ +/** + * @file + * + * Implement a modal form. + * + * @see modal.inc for documentation. + * + * This javascript relies on the CTools ajax responder. + */ + +(function ($) { + // Make sure our objects are defined. + Drupal.CTools = Drupal.CTools || {}; + Drupal.CTools.Modal = Drupal.CTools.Modal || {}; + + /** + * Display the modal + * + * @todo -- document the settings. + */ + Drupal.CTools.Modal.show = function(choice) { + var opts = {}; + + if (choice && typeof choice == 'string' && Drupal.settings[choice]) { + // This notation guarantees we are actually copying it. + $.extend(true, opts, Drupal.settings[choice]); + } + else if (choice) { + $.extend(true, opts, choice); + } + + var defaults = { + modalTheme: 'CToolsModalDialog', + throbberTheme: 'CToolsModalThrobber', + animation: 'show', + animationSpeed: 'fast', + modalSize: { + type: 'scale', + width: .8, + height: .8, + addWidth: 0, + addHeight: 0, + // How much to remove from the inner content to make space for the + // theming. + contentRight: 25, + contentBottom: 45 + }, + modalOptions: { + opacity: .55, + background: '#fff' + }, + modalClass: 'default' + }; + + var settings = {}; + $.extend(true, settings, defaults, Drupal.settings.CToolsModal, opts); + + if (Drupal.CTools.Modal.currentSettings && Drupal.CTools.Modal.currentSettings != settings) { + Drupal.CTools.Modal.modal.remove(); + Drupal.CTools.Modal.modal = null; + } + + Drupal.CTools.Modal.currentSettings = settings; + + var resize = function(e) { + // When creating the modal, it actually exists only in a theoretical + // place that is not in the DOM. But once the modal exists, it is in the + // DOM so the context must be set appropriately. + var context = e ? document : Drupal.CTools.Modal.modal; + + if (Drupal.CTools.Modal.currentSettings.modalSize.type == 'scale') { + var width = $(window).width() * Drupal.CTools.Modal.currentSettings.modalSize.width; + var height = $(window).height() * Drupal.CTools.Modal.currentSettings.modalSize.height; + } + else { + var width = Drupal.CTools.Modal.currentSettings.modalSize.width; + var height = Drupal.CTools.Modal.currentSettings.modalSize.height; + } + + // Use the additionol pixels for creating the width and height. + $('div.ctools-modal-content', context).css({ + 'width': width + Drupal.CTools.Modal.currentSettings.modalSize.addWidth + 'px', + 'height': height + Drupal.CTools.Modal.currentSettings.modalSize.addHeight + 'px' + }); + $('div.ctools-modal-content .modal-content', context).css({ + 'width': (width - Drupal.CTools.Modal.currentSettings.modalSize.contentRight) + 'px', + 'height': (height - Drupal.CTools.Modal.currentSettings.modalSize.contentBottom) + 'px' + }); + } + + if (!Drupal.CTools.Modal.modal) { + Drupal.CTools.Modal.modal = $(Drupal.theme(settings.modalTheme)); + if (settings.modalSize.type == 'scale') { + $(window).bind('resize', resize); + } + } + + resize(); + + $('span.modal-title', Drupal.CTools.Modal.modal).html(Drupal.CTools.Modal.currentSettings.loadingText); + Drupal.CTools.Modal.modalContent(Drupal.CTools.Modal.modal, settings.modalOptions, settings.animation, settings.animationSpeed, settings.modalClass); + $('#modalContent .modal-content').html(Drupal.theme(settings.throbberTheme)).addClass('ctools-modal-loading'); + + // Position autocomplete results based on the scroll position of the modal. + $('#modalContent .modal-content').delegate('input.form-autocomplete', 'keyup', function() { + $('#autocomplete').css('top', $(this).position().top + $(this).outerHeight() + $(this).offsetParent().filter('#modal-content').scrollTop()); + }); + }; + + /** + * Hide the modal + */ + Drupal.CTools.Modal.dismiss = function() { + if (Drupal.CTools.Modal.modal) { + Drupal.CTools.Modal.unmodalContent(Drupal.CTools.Modal.modal); + } + }; + + /** + * Provide the HTML to create the modal dialog. + */ + Drupal.theme.prototype.CToolsModalDialog = function () { + var html = '' + html += ' <div id="ctools-modal">' + html += ' <div class="ctools-modal-content">' // panels-modal-content + html += ' <div class="modal-header">'; + html += ' <a class="close" href="#">'; + html += Drupal.CTools.Modal.currentSettings.closeText + Drupal.CTools.Modal.currentSettings.closeImage; + html += ' </a>'; + html += ' <span id="modal-title" class="modal-title"> </span>'; + html += ' </div>'; + html += ' <div id="modal-content" class="modal-content">'; + html += ' </div>'; + html += ' </div>'; + html += ' </div>'; + + return html; + } + + /** + * Provide the HTML to create the throbber. + */ + Drupal.theme.prototype.CToolsModalThrobber = function () { + var html = ''; + html += ' <div id="modal-throbber">'; + html += ' <div class="modal-throbber-wrapper">'; + html += Drupal.CTools.Modal.currentSettings.throbber; + html += ' </div>'; + html += ' </div>'; + + return html; + }; + + /** + * Figure out what settings string to use to display a modal. + */ + Drupal.CTools.Modal.getSettings = function (object) { + var match = $(object).attr('class').match(/ctools-modal-(\S+)/); + if (match) { + return match[1]; + } + } + + /** + * Click function for modals that can be cached. + */ + Drupal.CTools.Modal.clickAjaxCacheLink = function () { + Drupal.CTools.Modal.show(Drupal.CTools.Modal.getSettings(this)); + return Drupal.CTools.AJAX.clickAJAXCacheLink.apply(this); + }; + + /** + * Handler to prepare the modal for the response + */ + Drupal.CTools.Modal.clickAjaxLink = function () { + Drupal.CTools.Modal.show(Drupal.CTools.Modal.getSettings(this)); + return false; + }; + + /** + * Submit responder to do an AJAX submit on all modal forms. + */ + Drupal.CTools.Modal.submitAjaxForm = function(e) { + var $form = $(this); + var url = $form.attr('action'); + + setTimeout(function() { Drupal.CTools.AJAX.ajaxSubmit($form, url); }, 1); + return false; + } + + /** + * Bind links that will open modals to the appropriate function. + */ + Drupal.behaviors.ZZCToolsModal = { + attach: function(context) { + // Bind links + // Note that doing so in this order means that the two classes can be + // used together safely. + /* + * @todo remimplement the warm caching feature + $('a.ctools-use-modal-cache', context).once('ctools-use-modal', function() { + $(this).click(Drupal.CTools.Modal.clickAjaxCacheLink); + Drupal.CTools.AJAX.warmCache.apply(this); + }); + */ + + $('area.ctools-use-modal, a.ctools-use-modal', context).once('ctools-use-modal', function() { + var $this = $(this); + $this.click(Drupal.CTools.Modal.clickAjaxLink); + // Create a drupal ajax object + var element_settings = {}; + if ($this.attr('href')) { + element_settings.url = $this.attr('href'); + element_settings.event = 'click'; + element_settings.progress = { type: 'throbber' }; + } + var base = $this.attr('href'); + Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings); + }); + + // Bind buttons + $('input.ctools-use-modal, button.ctools-use-modal', context).once('ctools-use-modal', function() { + var $this = $(this); + $this.click(Drupal.CTools.Modal.clickAjaxLink); + var button = this; + var element_settings = {}; + + // AJAX submits specified in this manner automatically submit to the + // normal form action. + element_settings.url = Drupal.CTools.Modal.findURL(this); + if (element_settings.url == '') { + element_settings.url = $(this).closest('form').attr('action'); + } + element_settings.event = 'click'; + element_settings.setClick = true; + + var base = $this.attr('id'); + Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings); + + // Make sure changes to settings are reflected in the URL. + $('.' + $(button).attr('id') + '-url').change(function() { + Drupal.ajax[base].options.url = Drupal.CTools.Modal.findURL(button); + }); + }); + + // Bind our custom event to the form submit + $('#modal-content form', context).once('ctools-use-modal', function() { + var $this = $(this); + var element_settings = {}; + + element_settings.url = $this.attr('action'); + element_settings.event = 'submit'; + element_settings.progress = { 'type': 'throbber' } + var base = $this.attr('id'); + + Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings); + Drupal.ajax[base].form = $this; + + $('input[type=submit], button', this).click(function(event) { + Drupal.ajax[base].element = this; + this.form.clk = this; + // Stop autocomplete from submitting. + if (Drupal.autocompleteSubmit && !Drupal.autocompleteSubmit()) { + return false; + } + // An empty event means we were triggered via .click() and + // in jquery 1.4 this won't trigger a submit. + if (event.bubbles == undefined) { + $(this.form).trigger('submit'); + return false; + } + }); + }); + + // Bind a click handler to allow elements with the 'ctools-close-modal' + // class to close the modal. + $('.ctools-close-modal', context).once('ctools-close-modal') + .click(function() { + Drupal.CTools.Modal.dismiss(); + return false; + }); + } + }; + + // The following are implementations of AJAX responder commands. + + /** + * AJAX responder command to place HTML within the modal. + */ + Drupal.CTools.Modal.modal_display = function(ajax, response, status) { + if ($('#modalContent').length == 0) { + Drupal.CTools.Modal.show(Drupal.CTools.Modal.getSettings(ajax.element)); + } + $('#modal-title').html(response.title); + // Simulate an actual page load by scrolling to the top after adding the + // content. This is helpful for allowing users to see error messages at the + // top of a form, etc. + $('#modal-content').html(response.output).scrollTop(0); + + // Attach behaviors within a modal dialog. + var settings = response.settings || ajax.settings || Drupal.settings; + Drupal.attachBehaviors('#modalContent', settings); + + if ($('#modal-content').hasClass('ctools-modal-loading')) { + $('#modal-content').removeClass('ctools-modal-loading'); + } + else { + // If the modal was already shown, and we are simply replacing its + // content, then focus on the first focusable element in the modal. + // (When first showing the modal, focus will be placed on the close + // button by the show() function called above.) + $('#modal-content :focusable:first').focus(); + } + } + + /** + * AJAX responder command to dismiss the modal. + */ + Drupal.CTools.Modal.modal_dismiss = function(command) { + Drupal.CTools.Modal.dismiss(); + $('link.ctools-temporary-css').remove(); + } + + /** + * Display loading + */ + //Drupal.CTools.AJAX.commands.modal_loading = function(command) { + Drupal.CTools.Modal.modal_loading = function(command) { + Drupal.CTools.Modal.modal_display({ + output: Drupal.theme(Drupal.CTools.Modal.currentSettings.throbberTheme), + title: Drupal.CTools.Modal.currentSettings.loadingText + }); + } + + /** + * Find a URL for an AJAX button. + * + * The URL for this gadget will be composed of the values of items by + * taking the ID of this item and adding -url and looking for that + * class. They need to be in the form in order since we will + * concat them all together using '/'. + */ + Drupal.CTools.Modal.findURL = function(item) { + var url = ''; + var url_class = '.' + $(item).attr('id') + '-url'; + $(url_class).each( + function() { + var $this = $(this); + if (url && $this.val()) { + url += '/'; + } + url += $this.val(); + }); + return url; + }; + + + /** + * modalContent + * @param content string to display in the content box + * @param css obj of css attributes + * @param animation (fadeIn, slideDown, show) + * @param speed (valid animation speeds slow, medium, fast or # in ms) + * @param modalClass class added to div#modalContent + */ + Drupal.CTools.Modal.modalContent = function(content, css, animation, speed, modalClass) { + // If our animation isn't set, make it just show/pop + if (!animation) { + animation = 'show'; + } + else { + // If our animation isn't "fadeIn" or "slideDown" then it always is show + if (animation != 'fadeIn' && animation != 'slideDown') { + animation = 'show'; + } + } + + if (!speed) { + speed = 'fast'; + } + + // Build our base attributes and allow them to be overriden + css = jQuery.extend({ + position: 'absolute', + left: '0px', + margin: '0px', + background: '#000', + opacity: '.55' + }, css); + + // Add opacity handling for IE. + css.filter = 'alpha(opacity=' + (100 * css.opacity) + ')'; + content.hide(); + + // If we already have modalContent, remove it. + if ($('#modalBackdrop').length) $('#modalBackdrop').remove(); + if ($('#modalContent').length) $('#modalContent').remove(); + + // position code lifted from http://www.quirksmode.org/viewport/compatibility.html + if (self.pageYOffset) { // all except Explorer + var wt = self.pageYOffset; + } else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6 Strict + var wt = document.documentElement.scrollTop; + } else if (document.body) { // all other Explorers + var wt = document.body.scrollTop; + } + + // Get our dimensions + + // Get the docHeight and (ugly hack) add 50 pixels to make sure we dont have a *visible* border below our div + var docHeight = $(document).height() + 50; + var docWidth = $(document).width(); + var winHeight = $(window).height(); + var winWidth = $(window).width(); + if( docHeight < winHeight ) docHeight = winHeight; + + // Create our divs + $('body').append('<div id="modalBackdrop" class="backdrop-' + modalClass + '" style="z-index: 1000; display: none;"></div><div id="modalContent" class="modal-' + modalClass + '" style="z-index: 1001; position: absolute;">' + $(content).html() + '</div>'); + + // Get a list of the tabbable elements in the modal content. + var getTabbableElements = function () { + var tabbableElements = $('#modalContent :tabbable'), + radioButtons = tabbableElements.filter('input[type="radio"]'); + + // The list of tabbable elements from jQuery is *almost* right. The + // exception is with groups of radio buttons. The list from jQuery will + // include all radio buttons, when in fact, only the selected radio button + // is tabbable, and if no radio buttons in a group are selected, then only + // the first is tabbable. + if (radioButtons.length > 0) { + // First, build up an index of which groups have an item selected or not. + var anySelected = {}; + radioButtons.each(function () { + var name = this.name; + + if (typeof anySelected[name] === 'undefined') { + anySelected[name] = radioButtons.filter('input[name="' + name + '"]:checked').length !== 0; + } + }); + + // Next filter out the radio buttons that aren't really tabbable. + var found = {}; + tabbableElements = tabbableElements.filter(function () { + var keep = true; + + if (this.type == 'radio') { + if (anySelected[this.name]) { + // Only keep the selected one. + keep = this.checked; + } + else { + // Only keep the first one. + if (found[this.name]) { + keep = false; + } + found[this.name] = true; + } + } + + return keep; + }); + } + + return tabbableElements.get(); + }; + + // Keyboard and focus event handler ensures only modal elements gain focus. + modalEventHandler = function( event ) { + target = null; + if ( event ) { //Mozilla + target = event.target; + } else { //IE + event = window.event; + target = event.srcElement; + } + + var parents = $(target).parents().get(); + for (var i = 0; i < parents.length; ++i) { + var position = $(parents[i]).css('position'); + if (position == 'absolute' || position == 'fixed') { + return true; + } + } + + if ($(target).is('#modalContent, body') || $(target).filter('*:visible').parents('#modalContent').length) { + // Allow the event only if target is a visible child node + // of #modalContent. + return true; + } + else { + getTabbableElements()[0].focus(); + } + + event.preventDefault(); + }; + $('body').bind( 'focus', modalEventHandler ); + $('body').bind( 'keypress', modalEventHandler ); + + // Keypress handler Ensures you can only TAB to elements within the modal. + // Based on the psuedo-code from WAI-ARIA 1.0 Authoring Practices section + // 3.3.1 "Trapping Focus". + modalTabTrapHandler = function (evt) { + // We only care about the TAB key. + if (evt.which != 9) { + return true; + } + + var tabbableElements = getTabbableElements(), + firstTabbableElement = tabbableElements[0], + lastTabbableElement = tabbableElements[tabbableElements.length - 1], + singleTabbableElement = firstTabbableElement == lastTabbableElement, + node = evt.target; + + // If this is the first element and the user wants to go backwards, then + // jump to the last element. + if (node == firstTabbableElement && evt.shiftKey) { + if (!singleTabbableElement) { + lastTabbableElement.focus(); + } + return false; + } + // If this is the last element and the user wants to go forwards, then + // jump to the first element. + else if (node == lastTabbableElement && !evt.shiftKey) { + if (!singleTabbableElement) { + firstTabbableElement.focus(); + } + return false; + } + // If this element isn't in the dialog at all, then jump to the first + // or last element to get the user into the game. + else if ($.inArray(node, tabbableElements) == -1) { + // Make sure the node isn't in another modal (ie. WYSIWYG modal). + var parents = $(node).parents().get(); + for (var i = 0; i < parents.length; ++i) { + var position = $(parents[i]).css('position'); + if (position == 'absolute' || position == 'fixed') { + return true; + } + } + + if (evt.shiftKey) { + lastTabbableElement.focus(); + } + else { + firstTabbableElement.focus(); + } + } + }; + $('body').bind('keydown', modalTabTrapHandler); + + // Create our content div, get the dimensions, and hide it + var modalContent = $('#modalContent').css('top','-1000px'); + var mdcTop = wt + ( winHeight / 2 ) - ( modalContent.outerHeight() / 2); + var mdcLeft = ( winWidth / 2 ) - ( modalContent.outerWidth() / 2); + $('#modalBackdrop').css(css).css('top', 0).css('height', docHeight + 'px').css('width', docWidth + 'px').show(); + modalContent.css({top: mdcTop + 'px', left: mdcLeft + 'px'}).hide()[animation](speed); + + // Bind a click for closing the modalContent + modalContentClose = function(){close(); return false;}; + $('.close').bind('click', modalContentClose); + + // Bind a keypress on escape for closing the modalContent + modalEventEscapeCloseHandler = function(event) { + if (event.keyCode == 27) { + close(); + return false; + } + }; + + $(document).bind('keydown', modalEventEscapeCloseHandler); + + // Per WAI-ARIA 1.0 Authoring Practices, initial focus should be on the + // close button, but we should save the original focus to restore it after + // the dialog is closed. + var oldFocus = document.activeElement; + $('.close').focus(); + + // Close the open modal content and backdrop + function close() { + // Unbind the events + $(window).unbind('resize', modalContentResize); + $('body').unbind( 'focus', modalEventHandler); + $('body').unbind( 'keypress', modalEventHandler ); + $('body').unbind( 'keydown', modalTabTrapHandler ); + $('.close').unbind('click', modalContentClose); + $('body').unbind('keypress', modalEventEscapeCloseHandler); + $(document).trigger('CToolsDetachBehaviors', $('#modalContent')); + + // Set our animation parameters and use them + if ( animation == 'fadeIn' ) animation = 'fadeOut'; + if ( animation == 'slideDown' ) animation = 'slideUp'; + if ( animation == 'show' ) animation = 'hide'; + + // Close the content + modalContent.hide()[animation](speed); + + // Remove the content + $('#modalContent').remove(); + $('#modalBackdrop').remove(); + + // Restore focus to where it was before opening the dialog + $(oldFocus).focus(); + }; + + // Move and resize the modalBackdrop and modalContent on window resize. + modalContentResize = function(){ + + // Reset the backdrop height/width to get accurate document size. + $('#modalBackdrop').css('height', '').css('width', ''); + + // Position code lifted from: + // http://www.quirksmode.org/viewport/compatibility.html + if (self.pageYOffset) { // all except Explorer + var wt = self.pageYOffset; + } else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6 Strict + var wt = document.documentElement.scrollTop; + } else if (document.body) { // all other Explorers + var wt = document.body.scrollTop; + } + + // Get our heights + var docHeight = $(document).height(); + var docWidth = $(document).width(); + var winHeight = $(window).height(); + var winWidth = $(window).width(); + if( docHeight < winHeight ) docHeight = winHeight; + + // Get where we should move content to + var modalContent = $('#modalContent'); + var mdcTop = wt + ( winHeight / 2 ) - ( modalContent.outerHeight() / 2); + var mdcLeft = ( winWidth / 2 ) - ( modalContent.outerWidth() / 2); + + // Apply the changes + $('#modalBackdrop').css('height', docHeight + 'px').css('width', docWidth + 'px').show(); + modalContent.css('top', mdcTop + 'px').css('left', mdcLeft + 'px').show(); + }; + $(window).bind('resize', modalContentResize); + }; + + /** + * unmodalContent + * @param content (The jQuery object to remove) + * @param animation (fadeOut, slideUp, show) + * @param speed (valid animation speeds slow, medium, fast or # in ms) + */ + Drupal.CTools.Modal.unmodalContent = function(content, animation, speed) + { + // If our animation isn't set, make it just show/pop + if (!animation) { var animation = 'show'; } else { + // If our animation isn't "fade" then it always is show + if (( animation != 'fadeOut' ) && ( animation != 'slideUp')) animation = 'show'; + } + // Set a speed if we dont have one + if ( !speed ) var speed = 'fast'; + + // Unbind the events we bound + $(window).unbind('resize', modalContentResize); + $('body').unbind('focus', modalEventHandler); + $('body').unbind('keypress', modalEventHandler); + $('body').unbind( 'keydown', modalTabTrapHandler ); + $('.close').unbind('click', modalContentClose); + $('body').unbind('keypress', modalEventEscapeCloseHandler); + $(document).trigger('CToolsDetachBehaviors', $('#modalContent')); + + // jQuery magic loop through the instances and run the animations or removal. + content.each(function(){ + if ( animation == 'fade' ) { + $('#modalContent').fadeOut(speed, function() { + $('#modalBackdrop').fadeOut(speed, function() { + $(this).remove(); + }); + $(this).remove(); + }); + } else { + if ( animation == 'slide' ) { + $('#modalContent').slideUp(speed,function() { + $('#modalBackdrop').slideUp(speed, function() { + $(this).remove(); + }); + $(this).remove(); + }); + } else { + $('#modalContent').remove(); + $('#modalBackdrop').remove(); + } + } + }); + }; + +$(function() { + Drupal.ajax.prototype.commands.modal_display = Drupal.CTools.Modal.modal_display; + Drupal.ajax.prototype.commands.modal_dismiss = Drupal.CTools.Modal.modal_dismiss; +}); + +})(jQuery); diff --git a/sites/all/modules/ctools/js/states-show.js b/sites/all/modules/ctools/js/states-show.js new file mode 100644 index 000000000..88df58419 --- /dev/null +++ b/sites/all/modules/ctools/js/states-show.js @@ -0,0 +1,43 @@ +/** + * @file + * Custom state for handling visibility + */ + +/** + * Add a new state to Drupal #states. We use this to toggle element-invisible + * to show/hidden #states elements. This allows elements to be visible to + * screen readers. + * + * To use: + * $form['my_form_field'] = array( + * .. + * // Only show this field if 'some_other_field' is checked. + * '#states => array( + * 'show' => array( + * 'some-other-field' => array('checked' => TRUE), + * ), + * ), + * .. + * // Required to load the 'show' state handler. + * '#attached' => array( + * 'js' => array(ctools_attach_js('states-show')), + * ), + * ); + */ + +(function ($) { + 'use strict'; + + Drupal.states.State.aliases.hidden = '!show'; + + // Show/hide form items by toggling the 'element-invisible' class. This is a + // more accessible option than the core 'visible' state. + $(document).bind('state:show', function(e) { + if (e.trigger) { + var element = $(e.target).closest('.form-item, .form-submit, .form-wrapper'); + element.toggle(e.value); + e.value === true ? element.removeClass('element-invisible') : element.addClass('element-invisible'); + } + }); + +})(jQuery); diff --git a/sites/all/modules/ctools/js/stylizer.js b/sites/all/modules/ctools/js/stylizer.js new file mode 100644 index 000000000..16d6c49d5 --- /dev/null +++ b/sites/all/modules/ctools/js/stylizer.js @@ -0,0 +1,220 @@ + +(function ($) { + Drupal.CTools = Drupal.CTools || {}; + Drupal.CTools.Stylizer = {}; + + Drupal.CTools.Stylizer.addFarbtastic = function(context) { + // This behavior attaches by ID, so is only valid once on a page. + if ($('ctools_stylizer_color_scheme_form .color-form.Stylizer-processed').size()) { + return; + } + + var form = $('.color-form', context); + var inputs = []; + var hooks = []; + var locks = []; + var focused = null; + + // Add Farbtastic + $(form).prepend('<div id="placeholder"></div>').addClass('color-processed'); + var farb = $.farbtastic('#placeholder'); + + // Decode reference colors to HSL + /*var reference = Drupal.settings.Stylizer.reference.clone(); + for (i in reference) { + reference[i] = farb.RGBToHSL(farb.unpack(reference[i])); + } */ + + // Set up colorscheme selector + $('#edit-scheme', form).change(function () { + var colors = this.options[this.selectedIndex].value; + if (colors != '') { + colors = colors.split(','); + for (i in colors) { + callback(inputs[i], colors[i], false, true); + } + } + }); + + /** + * Shift a given color, using a reference pair (ref in HSL). + * + * This algorithm ensures relative ordering on the saturation and luminance + * axes is preserved, and performs a simple hue shift. + * + * It is also symmetrical. If: shift_color(c, a, b) == d, + * then shift_color(d, b, a) == c. + */ + function shift_color(given, ref1, ref2) { + // Convert to HSL + given = farb.RGBToHSL(farb.unpack(given)); + + // Hue: apply delta + given[0] += ref2[0] - ref1[0]; + + // Saturation: interpolate + if (ref1[1] == 0 || ref2[1] == 0) { + given[1] = ref2[1]; + } + else { + var d = ref1[1] / ref2[1]; + if (d > 1) { + given[1] /= d; + } + else { + given[1] = 1 - (1 - given[1]) * d; + } + } + + // Luminance: interpolate + if (ref1[2] == 0 || ref2[2] == 0) { + given[2] = ref2[2]; + } + else { + var d = ref1[2] / ref2[2]; + if (d > 1) { + given[2] /= d; + } + else { + given[2] = 1 - (1 - given[2]) * d; + } + } + + return farb.pack(farb.HSLToRGB(given)); + } + + /** + * Callback for Farbtastic when a new color is chosen. + */ + function callback(input, color, propagate, colorscheme) { + // Set background/foreground color + $(input).css({ + backgroundColor: color, + 'color': farb.RGBToHSL(farb.unpack(color))[2] > 0.5 ? '#000' : '#fff' + }); + + // Change input value + if (input.value && input.value != color) { + input.value = color; + + // Update locked values + if (propagate) { + var i = input.i; + for (j = i + 1; ; ++j) { + if (!locks[j - 1] || $(locks[j - 1]).is('.unlocked')) break; + var matched = shift_color(color, reference[input.key], reference[inputs[j].key]); + callback(inputs[j], matched, false); + } + for (j = i - 1; ; --j) { + if (!locks[j] || $(locks[j]).is('.unlocked')) break; + var matched = shift_color(color, reference[input.key], reference[inputs[j].key]); + callback(inputs[j], matched, false); + } + + } + + // Reset colorscheme selector + if (!colorscheme) { + resetScheme(); + } + } + + } + + /** + * Reset the color scheme selector. + */ + function resetScheme() { + $('#edit-scheme', form).each(function () { + this.selectedIndex = this.options.length - 1; + }); + } + + // Focus the Farbtastic on a particular field. + function focus() { + var input = this; + // Remove old bindings + focused && $(focused).unbind('keyup', farb.updateValue) + .unbind('keyup', resetScheme) + .parent().removeClass('item-selected'); + + // Add new bindings + focused = this; + farb.linkTo(function (color) { callback(input, color, true, false); }); + farb.setColor(this.value); + $(focused).keyup(farb.updateValue).keyup(resetScheme) + .parent().addClass('item-selected'); + } + + // Initialize color fields + $('#palette input.form-text', form) + .each(function () { + // Extract palette field name + this.key = this.id.substring(13); + + // Link to color picker temporarily to initialize. + farb.linkTo(function () {}).setColor('#000').linkTo(this); + + // Add lock + var i = inputs.length; + if (inputs.length) { + var lock = $('<div class="lock"></div>').toggle( + function () { + $(this).addClass('unlocked'); + $(hooks[i - 1]).attr('class', + locks[i - 2] && $(locks[i - 2]).is(':not(.unlocked)') ? 'hook up' : 'hook' + ); + $(hooks[i]).attr('class', + locks[i] && $(locks[i]).is(':not(.unlocked)') ? 'hook down' : 'hook' + ); + }, + function () { + $(this).removeClass('unlocked'); + $(hooks[i - 1]).attr('class', + locks[i - 2] && $(locks[i - 2]).is(':not(.unlocked)') ? 'hook both' : 'hook down' + ); + $(hooks[i]).attr('class', + locks[i] && $(locks[i]).is(':not(.unlocked)') ? 'hook both' : 'hook up' + ); + } + ); + $(this).after(lock); + locks.push(lock); + }; + + // Add hook + var $this = $(this); + var hook = $('<div class="hook"></div>'); + $this.after(hook); + hooks.push(hook); + + $this.parent().find('.lock').click(); + this.i = i; + inputs.push(this); + }) + .focus(focus); + + $('#palette label', form); + + // Focus first color + focus.call(inputs[0]); + }; + + Drupal.behaviors.CToolsColorSettings = { + attach: function() { + $('.ctools-stylizer-color-edit:not(.ctools-color-processed)') + .addClass('ctools-color-processed') + .each(function() { + Drupal.CTools.Stylizer.addFarbtastic('#' + $(this).attr('id')); + }); + + $('div.form-item div.ctools-style-icon:not(.ctools-color-processed)') + .addClass('ctools-color-processed') + .click(function() { + $widget = $('input', $(this).parent()); + // Toggle if a checkbox, turn on if a radio. + $widget.attr('checked', !$widget.attr('checked') || $widget.is('input[type=radio]')); + }); + } + } +})(jQuery); |