diff --git a/sites/all/modules/contrib/admin/taxonomy_wrangler/LICENSE.txt b/sites/all/modules/contrib/admin/taxonomy_wrangler/LICENSE.txt new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/sites/all/modules/contrib/admin/taxonomy_wrangler/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sites/all/modules/contrib/admin/taxonomy_wrangler/css/taxonomy_wrangler.css b/sites/all/modules/contrib/admin/taxonomy_wrangler/css/taxonomy_wrangler.css new file mode 100644 index 00000000..277385fb --- /dev/null +++ b/sites/all/modules/contrib/admin/taxonomy_wrangler/css/taxonomy_wrangler.css @@ -0,0 +1,21 @@ +/** + * @file + * Styles for taxonomy wrangler + */ +.taxonomy-wrangler-accordion-item { + float: left; + font-size: 1.35em; + min-width: 15px; + padding-right: 5px; + padding-left: 5px; + color: #656565; +} +.taxonomy-wrangler-accordion-item:hover { + text-decoration: none; +} +.taxonomy-wrangler-accordion-item--parent span:after { + content: "\25be"; /*right pointing small triangle*/ +} +.taxonomy-wrangler-accordion-item--parent.is-collapsed span:after { + content: "\25b8"; /*down pointing small triangle*/ +} diff --git a/sites/all/modules/contrib/admin/taxonomy_wrangler/js/taxonomy_wrangler.js b/sites/all/modules/contrib/admin/taxonomy_wrangler/js/taxonomy_wrangler.js new file mode 100644 index 00000000..a9498c2b --- /dev/null +++ b/sites/all/modules/contrib/admin/taxonomy_wrangler/js/taxonomy_wrangler.js @@ -0,0 +1,591 @@ +/** + * @file + * js for taxonomy ui tweaks + */ + +(function ($, Drupal, window, document, undefined) { + + function TaxonomyWranglerUID() { + var prefix = 'taxonomy-wrangler-'; + var randomId = prefix + this.count; + while($('#' + randomId).length) { + this.count++; + randomId = prefix + this.count; + } + return randomId; + } + TaxonomyWranglerUID.count = 0; + + function TaxonomyWranglerAccordion(table) { + this.table = table; + this.rows = $('tbody tr', table); + this.init(); + } + + TaxonomyWranglerAccordion.instances = {}; + + var ctrlDown = false; + + $(window).keydown(function(e) { + if (e.keyCode == 17) { + ctrlDown = true; + } + }).keyup(function(e) { + if (e.keyCode == 17) { + ctrlDown = false; + } + }); + + TaxonomyWranglerAccordion.prototype.init = function() { + var accordion = this; + $('tbody tr').not(':eq(0)').each(function(){ + var $row = $(this); + accordion.collapseTree($row); + accordion.attachAccordionEvents($row); + }); + }; + + TaxonomyWranglerAccordion.prototype.attachAccordionEvents = function($row) { + var accordion = this; + $row.find('.taxonomy-wrangler-accordion-item').bind('click.tweaksAccordion', function(e){ + e.preventDefault(); + if (ctrlDown) { + accordion.toggleTree($row); + } + else { + accordion.toggleRow($row); + } + return false; + }); + } + + TaxonomyWranglerAccordion.prototype.refreshRows = function(){ + var accordion = this; + this.rows.not(':eq(0)').each(function(){ + var $this = $(this); + var hasChildren = accordion.rows.filter('[data-parent="'+ $this.data('tid') +'"]').length > 0 ? true : false; + var $collapseLink = $this.find('.taxonomy-wrangler-accordion-item--parent'); + //create a new parent + if (hasChildren && !$collapseLink.length) { + $this.find('.tabledrag-handle').after(''); + accordion.attachAccordionEvents($this); + } + else if (!hasChildren && $collapseLink.length) { + $collapseLink.remove(); + } + }); + }; + + /** + * This is a bit different than a typical accordion in that the + * data structure is hierarchical, but its representation in the DOM is flat + */ + TaxonomyWranglerAccordion.prototype.showRow = function($row) { + $row.css('display', 'table-row').removeClass('tabledrag-hidden'); + } + + TaxonomyWranglerAccordion.prototype.hideRow = function($row) { + $row.css('display', 'none').addClass('tabledrag-hidden'); + } + + TaxonomyWranglerAccordion.prototype.setCollapsed = function($row, collapsed, recurse) { + var accordion = this; + var $children = this.rows.filter('[data-parent=' + $row.data('tid') + ']'); + if (collapsed && $children.length) { + $row.data('is-collapsed', true); + $row.find('.taxonomy-wrangler-accordion-item').addClass('is-collapsed'); + if ($children.length) { + $row.addClass('tabledrag-leaf'); + } + } + else { + $row.data('is-collapsed', false).removeClass('tabledrag-leaf'); + $row.find('.taxonomy-wrangler-accordion-item').removeClass('is-collapsed'); + } + if (recurse) { + accordion.rows.filter('[data-parent=' + $row.data('tid') + ']').each(function(){ + accordion.setCollapsed($(this), collapsed, true); + }); + } + } + + /* + * Hide all children + */ + TaxonomyWranglerAccordion.prototype.hideChildren = function($row) { + var $item = $row.find('.taxonomy-wrangler-accordion-item'); + var accordion = this; + this.rows.filter('[data-parent=' + $row.data('tid') + ']').each(function(){ + accordion.hideRow($(this)); + accordion.hideChildren($(this)); + }); + }; + + /** + * Show children if they are not collapsed + */ + TaxonomyWranglerAccordion.prototype.showChildren = function($row) { + var accordion = this; + accordion.rows.filter('[data-parent=' + $row.data('tid') + ']').each(function(){ + accordion.showRow($(this)); + if (!$(this).data('is-collapsed')) { + accordion.showChildren($(this)); + } + }); + }; + + TaxonomyWranglerAccordion.prototype.toggleRow = function($row) { + if ($row.data('is-collapsed')) { + this.setCollapsed($row, false); + this.showChildren($row); + } + else { + this.setCollapsed($row, true); + this.hideChildren($row); + } + }; + + TaxonomyWranglerAccordion.prototype.expandTree = function($row) { + this.setCollapsed($row, false, true); + this.showChildren($row); + }; + + TaxonomyWranglerAccordion.prototype.collapseTree = function($row) { + this.setCollapsed($row, true, true); + this.hideChildren($row); + }; + + TaxonomyWranglerAccordion.prototype.toggleTree = function($row) { + if ($row.data('is-collapsed')) { + this.expandTree($row); + } + else { + this.collapseTree($row); + } + } + + Drupal.behaviors.taxonomyWranglerAccordion = { + attach: function(context, settings) { + idCount = 0; + + $('.taxonomy-wrangler-accordion', context) + .not('.taxonomy-wrangler-accordion-processed') + .addClass('taxonomy-wrangler-accordion-processed') + .each(function(){ + var id = $(this).attr('id') + if (!!!id || id == '') { + $(this).attr('id', TaxonomyWranglerUID()); + } + TaxonomyWranglerAccordion.instances[id] = new TaxonomyWranglerAccordion(this); + }); + } + }; + + function TaxonomyWranglerCollection() { + this.updatedTerms = {}; + }; + + TaxonomyWranglerCollection.prototype.updateTerm = function(row) { + var $row = $(row); + var tid = $row.data('tid'); + var now = Date.now(); + this.updatedTerms[tid] = { + "tid": $row.data('tid'), + "parent": $row.data('parent'), + "depth": $row.data('depth'), + "weight": $row.data('weight'), + "updated": now + } + //el.attr('data-{key}') & el.data('key') are not the same! + $row.attr('data-parent', $row.data('parent')) + .attr('data-depth', $row.data('depth')) + .attr('data-weight', $row.data('weight')) + .data('updated', now); + }; + + /** + * New TableDrag "class" that inherits Drupal.tableDrag + */ + function TaxonomyWranglerDrag(table, tableSettings) { + Drupal.tableDrag.call(this, table, tableSettings); + } + + TaxonomyWranglerDrag.prototype = new Drupal.tableDrag(); + + TaxonomyWranglerDrag.instances = {}; + + /** + * Find the target used within a particular row and group. + * This isn't strictly necessary in this case because the data + * is stored on the row element itself. + * Copied and modified from misc/tabledrag.js + */ + TaxonomyWranglerDrag.prototype.rowSettings = function (group, row) { + for (var delta in this.tableSettings[group]) { + // Return a copy of the row settings. + var rowSettings = {}; + for (var n in this.tableSettings[group][delta]) { + rowSettings[n] = this.tableSettings[group][delta][n]; + } + return rowSettings; + } + }; + + /** + * After the row is dropped, update the table fields according to the settings + * set for this table. + * + * @param changedRow + * DOM object for the row that was just dropped. + */ + TaxonomyWranglerDrag.prototype.updateFields = function (changedRow) { + var $c = $(changedRow); + for (var group in this.tableSettings) { + // Each group may have a different setting for relationship, so we find + // the source rows for each separately. + this.updateField(changedRow, group); + } + }; + + /** + * After the row is dropped, update a single table field according to specific + * settings. This code is cribbed from tabledrag.js and modified to + * use the data properties on the row object rather than input fields in the row. + * + * @param changedRow + * DOM object for the row that was just dropped. + * @param group + * The settings group on which field updates will occur. + */ + TaxonomyWranglerDrag.prototype.updateField = function (changedRow, group) { + var rowSettings = this.rowSettings(group, changedRow); + // Set the row as its own target. + if (rowSettings.relationship == 'self' || rowSettings.relationship == 'group') { + var sourceRow = changedRow; + } + // Siblings are easy, check previous and next rows. + else if (rowSettings.relationship == 'sibling') { + var previousRow = $(changedRow).prev('tr').get(0); + var nextRow = $(changedRow).next('tr').get(0); + var sourceRow = changedRow; + if ($(previousRow).is('.draggable') && $('.' + group, previousRow).length) { + if (this.indentEnabled) { + if ($('.indentations', previousRow).length == $('.indentations', changedRow)) { + sourceRow = previousRow; + } + } + else { + sourceRow = previousRow; + } + } + else if ($(nextRow).is('.draggable') && $('.' + group, nextRow).length) { + if (this.indentEnabled) { + if ($('.indentations', nextRow).length == $('.indentations', changedRow)) { + sourceRow = nextRow; + } + } + else { + sourceRow = nextRow; + } + } + } + // Parents, look up the tree until we find a field not in this group. + // Go up as many parents as indentations in the changed row. + else if (rowSettings.relationship == 'parent') { + var previousRow = $(changedRow).prev('tr'); + while (previousRow.length && $('.indentation', previousRow).length >= this.rowObject.indents) { + previousRow = previousRow.prev('tr'); + } + // If we found a row. + if (previousRow.length) { + sourceRow = previousRow[0]; + } + // Otherwise we went all the way to the left of the table without finding + // a parent, meaning this item has been placed at the root level. + else { + // Use the first row in the table as source, because it's guaranteed to + // be at the root level. Find the first item, then compare this row + // against it as a sibling. + sourceRow = $(this.table).find('tr.draggable:first').get(0); + if (sourceRow == this.rowObject.element) { + sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]).next('tr.draggable').get(0); + } + var useSibling = true; + } + } + + // Because we may have moved the row from one category to another, + // take a look at our sibling and borrow its sources and targets. + this.copyDragClasses(sourceRow, changedRow, group); + rowSettings = this.rowSettings(group, changedRow); + + // In the case that we're looking for a parent, but the row is at the top + // of the tree, copy our sibling's values. + if (useSibling) { + rowSettings.relationship = 'sibling'; + rowSettings.source = rowSettings.target; + } + var $changedRow = $(changedRow); + // Check if data exists in this row. + if (typeof $(sourceRow).data(rowSettings.target) !== 'undefined') { + switch (rowSettings.action) { + case 'depth': + // Get the depth of the target row. + $changedRow.data(rowSettings.target, $('.indentation', $(sourceRow)).length); + break; + case 'match': + // Update the value. + $changedRow.data(rowSettings.target, $(sourceRow).data(rowSettings.source)); + break; + case 'order': + var siblings = this.rowObject.findSiblings(rowSettings); + // We're not using fields for this so we just assume + // weight starting from 0 + var weight = 0; + $(siblings).each(function () { + $(this).data('weight', weight); + weight++; + $(this).trigger('taxonomyWranglerTermDrag.rowDataChange', [this]); + }); + break; + } + } + $changedRow.trigger('taxonomyWranglerTermDrag.rowDataChange', [changedRow]); + }; + + + TaxonomyWranglerDrag.prototype.dropRow = function (event, self) { + // Drop row functionality shared between mouseup and blur events. + if (self.rowObject != null) { + var droppedRow = self.rowObject.element; + // The row is already in the right place so we just release it. + if (self.rowObject.changed == true) { + // Update the fields in the dropped row. + self.updateFields(droppedRow); + + // If a setting exists for affecting the entire group, update all the + // fields in the entire dragged group. + for (var group in self.tableSettings) { + var rowSettings = self.rowSettings(group, droppedRow); + if (rowSettings.relationship == 'group') { + for (var n in self.rowObject.children) { + self.updateField(self.rowObject.children[n], group); + } + } + } + + self.rowObject.markChanged(); + if (self.changed == false) { + self.changed = true; + } + } + + if (self.indentEnabled) { + self.rowObject.removeIndentClasses(); + } + if (self.oldRowElement) { + $(self.oldRowElement).removeClass('drag-previous'); + } + $(droppedRow).removeClass('drag').addClass('drag-previous'); + self.oldRowElement = droppedRow; + self.onDrop(); + self.rowObject = null; + } + + // Functionality specific only to mouseup event. + if (self.dragObject != null) { + $('.tabledrag-handle', droppedRow).removeClass('tabledrag-handle-hover'); + + self.dragObject = null; + $('body').removeClass('drag'); + clearInterval(self.scrollInterval); + + // Hack for IE6 that flickers uncontrollably if select lists are moved. + if (navigator.userAgent.indexOf('MSIE 6.') != -1) { + $('select', this.table).css('display', 'block'); + } + } + }; + + /** + * Indent a row within the legal bounds of the table. + * + * @param indentDiff + * The number of additional indentations proposed for the row (can be + * positive or negative). This number will be adjusted to nearest valid + * indentation level for the row. + */ + TaxonomyWranglerDrag.prototype.row.prototype.indent = function (indentDiff) { + // Determine the valid indentations interval if not available yet. + if (!this.interval) { + var prevRow = $(this.element).prevAll('tr:not(.tabledrag-hidden)').get(0); + var nextRow = $(this.group).filter(':last').nextAll('tr:not(.tabledrag-hidden)').get(0); + this.interval = this.validIndentInterval(prevRow, nextRow); + } + + // Adjust to the nearest valid indentation. + var indent = this.indents + indentDiff; + indent = Math.max(indent, this.interval.min); + indent = Math.min(indent, this.interval.max); + indentDiff = indent - this.indents; + + for (var n = 1; n <= Math.abs(indentDiff); n++) { + // Add or remove indentations. + if (indentDiff < 0) { + $('.indentation:first', this.group).remove(); + this.indents--; + } + else { + $('td:first', this.group).prepend(Drupal.theme('tableDragIndentation')); + this.indents++; + } + } + if (indentDiff) { + // Update indentation for this row. + this.changed = true; + this.groupDepth += indentDiff; + this.onIndent(); + } + + return indentDiff; + }; + + /** + * Perform the swap between two rows. Take into account the collapsed + * accordion rows when swapping after. + * + * @param position + * Whether the swap will occur 'before' or 'after' the given row. + * @param row + * DOM element what will be swapped with the row group. + */ + TaxonomyWranglerDrag.prototype.row.prototype.swap = function (position, row) { + var swapRow = row; + if (position == 'after') { + var $row = $(row); + if ($row.data('is-collapsed')) { + var $target = $row.nextUntil(":visible"); + if ($target.length) { + swapRow = $target.get($target.length - 1); + } + else { + // If there are no other rows visible, we must be at the bottom + // of the table, get the last row. + swapRow = $row.nextAll(":last").get(0); + } + } + } + + Drupal.detachBehaviors(this.group, Drupal.settings, 'move'); + $(swapRow)[position](this.group); + Drupal.attachBehaviors(this.group, Drupal.settings); + this.changed = true; + this.onSwap(row); + }; + + /** + * Ensure that two rows are allowed to be swapped. + * + * @param row + * DOM object for the row being considered for swapping. + */ + TaxonomyWranglerDrag.prototype.row.prototype.isValidSwap = function (row) { + if (this.indentEnabled) { + var prevRow, nextRow; + if (this.direction == 'down') { + prevRow = row; + nextRow = $(row).nextAll('tr:not(:hidden)').get(0); + } + else { + prevRow = $(row).prevAll('tr:not(:hidden)').get(0); + nextRow = row; + } + this.interval = this.validIndentInterval(prevRow, nextRow); + + // We have an invalid swap if the valid indentations interval is empty. + if (this.interval.min > this.interval.max) { + return false; + } + } + + // Do not let an un-draggable first row have anything put before it. + if (this.table.tBodies[0].rows[0] == row && $(row).is(':not(.draggable)')) { + return false; + } + + return true; + }; + +/** + * Move a block in the blocks table from one region to another via select list. + * + * This behavior is dependent on the tableDrag behavior, since it uses the + * objects initialized in that behavior to update the row. + */ +Drupal.behaviors.taxonomyWranglerTermDrag = { + attach: function (context, settings) { + for (var base in settings.taxonomyWrangler.tables) { + if (typeof TaxonomyWranglerDrag.instances[base] == 'undefined') { + $('#' + base, context).once('tabledrag', function () { + // Create the new tableDrag instance. Save in the Drupal variable + // to allow other scripts access to the object. + TaxonomyWranglerDrag.instances[base] = new TaxonomyWranglerDrag(this, settings.taxonomyWrangler.tables[base]); + }); + } + } + var table = $('#taxonomy-wrangler', context); + var tableDrag = TaxonomyWranglerDrag.instances['taxonomy-wrangler']; // Get the blocks tableDrag object. + var $messages = $('#taxonomy-wrangler-messages-js', context); + + // Compare server timestamp with javascript changed stamp and remove changed classes. + if (settings.taxonomyWrangler && settings.taxonomyWrangler.updatedTimestamps) { + $.each(settings.taxonomyWrangler.updatedTimestamps, function (tid, stamp) { + var $row = table.find('tr[data-tid="'+ tid +'"]'); + if ($row.data('updated') == stamp) { + $row.removeClass('drag-previous').find('span.tabledrag-changed').remove(); + } + }); + } + + if (tableDrag.tm === null) { + $messages.removeClass('is-busy').empty(); + } + + if (!table.hasClass('taxonomy-wrangler-processed')) { + table.addClass('taxonomy-wrangler-processed'); + var rows = $('tr', table).length; + var $termData = $('input[name="term_data"]', context); + var $submit = $('input[name="op"][value="Save"]', context) + var $rows = $('tr', table); + tableDrag.collection = new TaxonomyWranglerCollection(); + var updatedTerms = {}; + $submit.attr('disabled', 'disabled'); + var accordion = !!TaxonomyWranglerAccordion.instances[table.attr('id')] ? TaxonomyWranglerAccordion.instances[table.attr('id')] : null; + + $rows.bind('taxonomyWranglerTermDrag.rowDataChange', function(e, row){ + tableDrag.collection.updateTerm(row); + var termsArray = []; + var tids = []; + accordion.refreshRows(); + $.each(tableDrag.collection.updatedTerms, function(i, val) { + termsArray.push(val); + tids.push(val.tid + ':' + val.weight); + }); + $termData.val(JSON.stringify({ "termData": termsArray })); + if (tableDrag.tm) { + clearTimeout(tableDrag.tm); + tableDrag.tm = null; + } + tableDrag.tm = setTimeout(function(){ + $submit.removeAttr('disabled').trigger('mousedown').attr('disabled', 'disabled'); + tableDrag.tm = null; + }, 1000); + if (!$messages.hasClass('is-busy')) { + $messages.addClass('is-busy').append('
 
Updating terms
'); + } + }); + } + } +}; + +})(jQuery, Drupal, this, this.document); diff --git a/sites/all/modules/contrib/admin/taxonomy_wrangler/taxonomy_wrangler.admin.inc b/sites/all/modules/contrib/admin/taxonomy_wrangler/taxonomy_wrangler.admin.inc new file mode 100644 index 00000000..3602e1c8 --- /dev/null +++ b/sites/all/modules/contrib/admin/taxonomy_wrangler/taxonomy_wrangler.admin.inc @@ -0,0 +1,291 @@ +vid); + } + + $form['#vocabulary'] = $vocabulary; + $form['#tree'] = TRUE; + $form['#parent_fields'] = FALSE; + + $form['header']['info'] = array( + '#markup' => '

' . t('Drag and drop taxonomy terms below. Ctrl + click on an arrow will expand the term and all its children') . '

', + ); + + $form['header']['messages'] = array( + '#markup' => '
', + ); + + $root_entries = 0; // Elements at the root level on this page. + + $delta = 0; + $term_deltas = array(); + $tree = taxonomy_get_tree($vocabulary->vid); + $form_state['storage']['tree'] = $tree; + $term = current($tree); + + $form['taxonomy_wrangler_messages'] = array( + '#type' => 'markup', + '#markup' => '', + ); + + // The original form handled setting the order after a validation error. + // Since we aren't supporting the non-ajax form, it has been removed. + + // Build the actual form. + foreach ($tree as $orig_key => $term) { + $term_deltas[$term->tid] = isset($term_deltas[$term->tid]) ? $term_deltas[$term->tid] + 1 : 0; + $key = 'tid:' . $term->tid . ':' . $term_deltas[$term->tid]; + // Save the term for the current page so we don't have to load it a second time. + $form[$key]['#term'] = (array) $term; + if (isset($term->parents)) { + $form[$key]['#term']['parent'] = $term->parent = $term->parents[0]; + unset($form[$key]['#term']['parents'], $term->parents); + } + + $form[$key]['view'] = array('#type' => 'link', '#title' => $term->name, '#href' => "taxonomy/term/$term->tid"); + if ($vocabulary->hierarchy < 2 && count($tree) > 1) { + $form['#parent_fields'] = TRUE; + $form[$key]['view']['#options']['attributes']['data-tid'] = $term->tid; + $form[$key]['view']['#options']['attributes']['data-parent'] = $term->tid; + $form[$key]['view']['#options']['attributes']['data-depth'] = $term->depth; + $form[$key]['view']['#options']['attributes']['data-weight'] = $term->weight; + } + $form[$key]['edit'] = array('#type' => 'link', '#title' => t('edit'), '#href' => 'taxonomy/term/' . $term->tid . '/edit', '#options' => array('query' => drupal_get_destination())); + } + + $form['#empty_text'] = t('No terms available. Add term.', array('@link' => url('admin/structure/taxonomy/' . $vocabulary->machine_name . '/add'))); + + // Hidden input field for ajax. + $form['term_data'] = array( + '#type' => 'hidden', + '#default_value' => '{}', + ); + + if ($vocabulary->hierarchy < 2 && count($tree) > 1) { + $form['actions'] = array('#type' => 'actions', '#tree' => FALSE); + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + '#ajax' => array( + 'callback' => 'taxonomy_wrangler_terms_overview_ajax_handler', + ), + ); + $form['actions']['reset_alphabetical'] = array( + '#type' => 'submit', + '#value' => t('Reset to alphabetical'), + ); + } + $module_path = drupal_get_path('module', 'taxonomy_wrangler'); + + $form['#attached']['css'][] = $module_path . '/css/taxonomy_wrangler.css'; + $form['#attached']['js'][] = $module_path . '/js/taxonomy_wrangler.js'; + + return $form; +} + +/** + * Validate callback for taxonomy_wrangler_overview_terms. + */ +function taxonomy_wrangler_overview_terms_validate($form, &$form_state) { + if ($form_state['triggering_element']['#value'] != t('Reset to alphabetical')) { + if (isset($form_state['values']['term_data'])) { + $updated_terms_json = json_decode($form_state['values']['term_data']); + if ($updated_terms_json == NULL) { + drupal_set_message(t('an error occurred parsing submitted values.'), 'error'); + } + else { + $form_state['storage']['updated_terms'] = array(); + $values_to_update = array('parent', 'weight'); + if (!empty($updated_terms_json->termData)) { + $form_state['storage']['raw_term_data'] = $updated_terms_json->termData; + foreach ($updated_terms_json->termData as $updated_term) { + $term = taxonomy_term_load($updated_term->tid); + foreach ($values_to_update as $prop) { + if (isset($updated_term->{$prop})) { + $term->{$prop} = (int) $updated_term->{$prop}; + } + } + $form_state['storage']['updated_terms'][$term->tid] = $term; + } + } + } + } + } +} + +/** + * Submit callback for taxonomy_wrangler_overview_terms. + */ +function taxonomy_wrangler_overview_terms_submit($form, &$form_state) { + if ($form_state['triggering_element']['#value'] == t('Reset to alphabetical')) { + module_load_include('inc', 'taxonomy', 'taxonomy.admin'); + // Execute the reset action. + if ($form_state['values']['reset_alphabetical'] === TRUE) { + return taxonomy_vocabulary_confirm_reset_alphabetical_submit($form, $form_state); + } + // Rebuild the form to confirm the reset action. + $form_state['rebuild'] = TRUE; + $form_state['confirm_reset_alphabetical'] = TRUE; + return; + } + else { + if (!empty($form_state['storage']['updated_terms'])) { + foreach ($form_state['storage']['updated_terms'] as $term) { + taxonomy_term_save($term); + } + } + } +} + +/** + * Ajax handler for taxonomy_wrangler_overview_terms. + */ +function taxonomy_wrangler_terms_overview_ajax_handler($form, &$form_state) { + $commands = array(); + $messages = '
'; + $messages .= theme('status_messages'); + $messages .= '
'; + $commands[] = ajax_command_replace('#taxonomy-wrangler-messages-server', $messages); + // Send timestamps back to browser so it can update the row status. + $timestamps = array(); + if (!empty($form_state['storage']['raw_term_data'])) { + foreach($form_state['storage']['raw_term_data'] as $term) { + if (isset($term->updated)) { + $timestamps[$term->tid] = (float) $term->updated; + } + } + } + $commands[] = ajax_command_settings(array( + 'taxonomyWrangler' => array('updatedTimestamps' => $timestamps), + )); + + return array('#type' => 'ajax', '#commands' => $commands); +} + +/** + * Returns HTML for a terms overview form as a sortable list of terms. + * + * @param $variables + * An associative array containing: + * - form: A render element representing the form. + * + * @see taxonomy_overview_terms() + * @ingroup themeable + */ +function theme_taxonomy_wrangler_overview_terms($variables) { + $form = $variables['form']; + + // Add drag and drop if parent fields are present in the form. + if ($form['#parent_fields']) { + taxonomy_wrangler_add_tabledrag('taxonomy-wrangler', 'match', 'parent', 'parent', 'parent', 'tid', FALSE); + taxonomy_wrangler_add_tabledrag('taxonomy-wrangler', 'depth', 'group', 'depth', NULL, NULL, FALSE); + drupal_add_css(drupal_get_path('module', 'taxonomy') . '/taxonomy.css'); + } + taxonomy_wrangler_add_tabledrag('taxonomy-wrangler', 'order', 'sibling', 'weight'); + + $errors = form_get_errors() != FALSE ? form_get_errors() : array(); + $rows = array(); + $element_children = element_children($form); + $is_root = TRUE; + foreach ($element_children as $i => $key) { + if (isset($form[$key]['#term'])) { + $term = &$form[$key]; + $next_key = isset($element_children[$i + 1]) ? $element_children[$i + 1] : FALSE; + $is_parent = FALSE; + if (isset($form[$next_key]['#term']) && $form[$next_key]['#term']['depth'] > $term['#term']['depth'] && !$is_root) { + $is_parent = TRUE; + } + $row = array(); + $row[] = (isset($term['#term']['depth']) && $term['#term']['depth'] > 0 ? theme('indentation', array('size' => $term['#term']['depth'])) : ''); + $accordion_classes = array('taxonomy-wrangler-accordion-item'); + if ($is_root) { + $accordion_classes[] = 'taxonomy-wrangler-accordion-item--root'; + $is_root = FALSE; + } + elseif ($is_parent) { + $accordion_classes[] = 'taxonomy-wrangler-accordion-item--parent'; + } + else { + $accordion_classes[] = 'taxonomy-wrangler-accordion-item--child'; + } + $row[0] .= ''; + $row[0] .= drupal_render($term['view']); + if ($form['#parent_fields']) { + $term['tid']['#attributes']['class'] = array('term-id'); + $term['parent']['#attributes']['class'] = array('term-parent'); + $term['depth']['#attributes']['class'] = array('term-depth'); + $row[0] .= drupal_render($term['parent']) . drupal_render($term['tid']) . drupal_render($term['depth']); + } + $term['weight']['#attributes']['class'] = array('term-weight'); + $row[] = drupal_render($term['weight']); + $row[] = drupal_render($term['edit']); + $row = array( + 'data' => $row, + 'data-tid' => $term['#term']['tid'], + 'data-parent' => $term['#term']['parent'], + 'data-depth' => $term['#term']['depth'], + 'data-weight' => $term['#term']['weight'], + 'data-haschildren' => $is_parent, + ); + $rows[$key] = $row; + } + } + + // Add necessary classes to rows. + $row_position = 0; + foreach ($rows as $key => $row) { + $rows[$key]['class'] = array(); + if (isset($form['#parent_fields'])) { + $rows[$key]['class'][] = 'draggable'; + } + + // Add an error class if this row contains a form error. + foreach ($errors as $error_key => $error) { + if (strpos($error_key, $key) === 0) { + $rows[$key]['class'][] = 'error'; + } + } + $rows[$key]['class'][] = 'taxonomy-wrangler-term-row'; + $rows[$key]['class'][] = 'term-weight-row'; + $row_position++; + } + + if (empty($rows)) { + $rows[] = array(array('data' => $form['#empty_text'], 'colspan' => '3')); + } + $output = drupal_render($form['header']); + //unset($form['header']); + $header = array(t('Name'), t('Weight'), t('Operations')); + $output .= theme('table', array( + 'header' => $header, + 'rows' => $rows, + 'attributes' => array( + 'id' => 'taxonomy-wrangler', + 'class' => array('taxonomy-wrangler', 'taxonomy-wrangler-accordion'), + ), + )); + $output .= drupal_render_children($form); + + return $output; +} diff --git a/sites/all/modules/contrib/admin/taxonomy_wrangler/taxonomy_wrangler.info b/sites/all/modules/contrib/admin/taxonomy_wrangler/taxonomy_wrangler.info new file mode 100644 index 00000000..dce449a3 --- /dev/null +++ b/sites/all/modules/contrib/admin/taxonomy_wrangler/taxonomy_wrangler.info @@ -0,0 +1,12 @@ +name = Taxonomy Wrangler +description = Provides a better UI for the taxonomy overview page +core = 7.x +dependencies[] = taxonomy +package = Taxonomy + +; Information added by Drupal.org packaging script on 2015-02-17 +version = "7.x-1.x-dev" +core = "7.x" +project = "taxonomy_wrangler" +datestamp = "1424205783" + diff --git a/sites/all/modules/contrib/admin/taxonomy_wrangler/taxonomy_wrangler.module b/sites/all/modules/contrib/admin/taxonomy_wrangler/taxonomy_wrangler.module new file mode 100644 index 00000000..e61a28c4 --- /dev/null +++ b/sites/all/modules/contrib/admin/taxonomy_wrangler/taxonomy_wrangler.module @@ -0,0 +1,59 @@ + array( + 'render element' => 'form', + ), + ); +} + +/** + * Copy of drupal_add_tabledrag(). + * + * This is a copy of drupal_add_tabledrag used just to instantiate + * our own tabledrag class vs Drupal's. + * @see drupal_add_tabledrag + */ +function taxonomy_wrangler_add_tabledrag($table_id, $action, $relationship, $group, $subgroup = NULL, $source = NULL, $hidden = TRUE, $limit = 0) { + $js_added = &drupal_static(__FUNCTION__, FALSE); + if (!$js_added) { + // Add the table drag JavaScript to the page before the module JavaScript + // to ensure that table drag behaviors are registered before any module + // uses it. + drupal_add_library('system', 'jquery.cookie'); + drupal_add_js('misc/tabledrag.js', array('weight' => -1)); + $js_added = TRUE; + } + + // If a subgroup or source isn't set, assume it is the same as the group. + $target = isset($subgroup) ? $subgroup : $group; + $source = isset($source) ? $source : $target; + $settings['taxonomyWrangler']['tables'][$table_id][$group][] = array( + 'target' => $target, + 'source' => $source, + 'relationship' => $relationship, + 'action' => $action, + 'hidden' => $hidden, + 'limit' => $limit, + ); + drupal_add_js($settings, 'setting'); +}