2 +--------------------------------------------------------------------+
4 +--------------------------------------------------------------------+
5 | Copyright CiviCRM LLC (c) 2004-2018 |
6 +--------------------------------------------------------------------+
7 | This file is a part of CiviCRM. |
9 | CiviCRM is free software; you can copy, modify, and distribute it |
10 | under the terms of the GNU Affero General Public License |
11 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | CiviCRM is distributed in the hope that it will be useful, but |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
16 | See the GNU Affero General Public License for more details. |
18 | You should have received a copy of the GNU Affero General Public |
19 | License and the CiviCRM Licensing Exception along |
20 | with this program; if not, contact CiviCRM LLC |
21 | at info[AT]civicrm[DOT]org. If you have questions about the |
22 | GNU Affero General Public License or the licensing of CiviCRM, |
23 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
24 +--------------------------------------------------------------------+
26 <div class="crm-content-block">
28 {ts 1=', '|implode:$usedFor}Tags are a convenient way to categorize data (%1).{/ts}
29 {if call_user_func(array('CRM_Core_Permission','check'), 'administer Tagsets')}
31 {ts}Create predefined tags in the main tree, or click the <strong>+</strong> to add a set for free tagging.{/ts}
33 {docURL page="user/organising-your-data/groups-and-tags"}
36 <div id="mainTabContainer">
38 <li class="ui-corner-all crm-tab-button" title="{ts}Main Tag List{/ts}">
39 <a href="#tree"><i class="crm-i fa-tags"></i> {ts}Tag Tree{/ts}</a>
41 {foreach from=$tagsets item=set}
42 <li class="ui-corner-all crm-tab-button {if ($set.is_reserved)}is-reserved{/if}" title="{ts 1=', '|implode:$set.used_for_label}Tag Set for %1{/ts}">
43 <a href="#tagset-{$set.id}">{$set.name}</a>
46 {if call_user_func(array('CRM_Core_Permission','check'), 'administer Tagsets')}
47 <li class="ui-corner-all crm-tab-button" title="{ts}Add Tag Set{/ts}">
48 <a href="#new-tagset"><i class="crm-i fa-plus"></i></a>
54 {ts}Organize the tag hierarchy by clicking and dragging. Shift-click to select multiple tags to merge/move/delete.{/ts}
56 <input class="crm-form-text big" name="filter_tag_tree" placeholder="{ts}Filter List{/ts}" allowclear="1"/>
57 <a class="crm-hover-button crm-clear-link" style="visibility:hidden;" title="{ts}Clear{/ts}"><i class="crm-i fa-times"></i></a>
59 {foreach from=$tagsets item=set}
60 <div id="tagset-{$set.id}">
67 {crmAPI entity="Contact" action="getsingle" var="user" return='display_name' id="user_contact_id"}
69 <script type="text/javascript">
72 var $window = $(window),
73 renderedTabs = ['tree'],
74 tagSets = {/literal}{$tagsets|@json_encode}{literal},
75 user = {/literal}{$user|@json_encode}{literal},
76 usedFor = {/literal}{$usedFor|@json_encode}{literal},
77 menuHeight = $('#civicrm-menu').height() + 15,
78 noneSelectedTpl = _.template($('#noneSelectedTpl').html()),
79 oneSelectedTpl = _.template($('#oneSelectedTpl').html()),
80 moreSelectedTpl = _.template($('#moreSelectedTpl').html()),
81 tagsetHeaderTpl = _.template($('#tagsetHeaderTpl').html());
83 function formatTagSet(info) {
84 info.date = CRM.utils.formatDate(info.created_date);
85 info.used_for_label = [];
86 if (undefined !== info.used_for) {
87 _.each(info.used_for.split(','), function(item) {
88 info.used_for_label.push(usedFor[item]);
93 _.each(tagSets, formatTagSet);
95 function renderTree($panel) {
98 tagset = $panel.attr('id').split('-')[1] || 0;
100 function hasChildren(id) {
101 var $node = $('.tag-tree', $panel).jstree(true).get_node(id, true);
102 return !$node.hasClass('jstree-leaf');
105 function changeColor() {
106 var color = $(this).val().toLowerCase(),
107 id = $(this).closest('.crm-entity').data('id'),
108 node = $('.tag-tree', $panel).jstree(true).get_node(id);
109 if (color === '#ffffff') {
110 node.a_attr.style = '';
112 node.a_attr.style = 'background-color: ' + color + '; color: ' + CRM.utils.colorContrast(color) + ';';
114 node.data.color = color;
115 $('.tag-tree', $panel).jstree(true).redraw(true);
116 CRM.api3('Tag', 'create', {id: id, color: color}, true);
119 function changeSelection(e, data) {
122 tagsetCount: _.keys(tagSets).length,
123 adminReserved: CRM.checkPerm('administer reserved tags')
125 tree = $('.tag-tree', $panel).jstree(true),
126 $infoBox = $('.tag-info', $panel);
127 selected = data.selected;
128 if (!data.selected || !data.selected.length) {
129 tplParams.is_reserved = tagset ? tagSets[tagset].is_reserved == 1 : false;
130 tplParams.length = $('.tag-tree li', $panel).length;
131 tplParams.adminTagsets = CRM.checkPerm('administer Tagsets');
132 $infoBox.html(noneSelectedTpl(tplParams));
133 } else if (data.selected.length === 1) {
134 tplParams.usedFor = usedFor;
135 tplParams.hasChildren = hasChildren(data.node.id);
136 $infoBox.html(oneSelectedTpl($.extend({}, data.node, tplParams)));
138 tplParams.items = data.selected;
139 tplParams.hasChildren = tplParams.reserved = tplParams.usages = 0;
140 _.each(data.selected, function(id) {
141 var node = tree.get_node(id);
142 tplParams.usages += node.data.usages;
143 tplParams.reserved += node.data.is_reserved;
144 tplParams.hasChildren += hasChildren(id) ? 1 : 0;
146 $infoBox.html(moreSelectedTpl(tplParams));
148 $infoBox.trigger('crmLoad');
151 function clearSelection(e) {
153 $('.tag-tree', $panel).jstree(true).deselect_all();
156 function changeUsedFor() {
157 var vals = $('input[name=used_for]:checked', $panel).map(function(i, el) {
160 id = $(this).closest('.crm-entity').data('id');
162 CRM.api3('Tag', 'create', {id: id, used_for: vals}, true);
163 var node = $('.tag-tree', $panel).jstree(true).get_node(id);
164 node.data.used_for = vals;
168 function moveTag(e, data) {
169 if (data.parent != data.old_parent) {
170 CRM.api3('Tag', 'create', {id: data.node.id, parent_id: data.parent.replace('#', '')}, true);
174 function deleteTagset() {
175 $('#mainTabContainer').tabs('option', 'active', 0);
176 $panel.off().remove();
177 $("a[href='#tagset-" + tagset + "']").parent().remove();
178 $('#mainTabContainer').tabs('refresh');
181 function updateTagset(info) {
182 tagSets[tagset].description = info.description;
183 tagSets[tagset].name = info.name;
184 tagSets[tagset].used_for = info.used_for;
185 tagSets[tagset].is_reserved = info.is_reserved;
186 formatTagSet(tagSets[tagset]);
188 $(".tag-tree", $panel).jstree("search", '');
191 function addTagsetHeader() {
192 $('.tagset-header', $panel).remove();
193 $panel.prepend(tagsetHeaderTpl(tagSets[tagset]));
194 $("a[href='#tagset-" + tagset + "']").text(tagSets[tagset].name)
195 .parent().toggleClass('is-reserved', tagSets[tagset].is_reserved == 1)
196 .attr('title', ts('{/literal}{ts escape='js' 1='%1'}Tag Set for %1{/ts}{literal}', {'1': tagSets[tagset].used_for_label.join(', ')}));
203 function moveTagDialog(e) {
205 var sets = [{key: '0', value: '{/literal}{ts escape='js'}Main Tag Tree{/ts}{literal}'}];
206 _.each(tagSets, function(tagSet) {
207 sets.push({key: tagSet.id, value: tagSet.name});
210 title: '{/literal}{ts escape='js'}Move to Tagset{/ts}{literal}',
211 message: '<label for="select-tagset">{/literal}{ts escape='js'}Select Tagset{/ts}{literal}: '
212 + '<select id="select-tagset" class="crm-select2 big">'
213 + CRM.utils.renderOptions(sets, tagset)
216 .on('crmConfirm:yes', function() {
217 var chosen = parseInt($('#select-tagset').val());
218 if (parseInt(tagset) !== chosen) {
220 _.each(selected, function(id) {
221 apiCalls.push(['Tag', 'create', {id: id, parent_id: chosen || ''}]);
223 $('#mainTabContainer').block();
224 CRM.api3(apiCalls, true)
226 $('.tag-tree', $panel).jstree(true).refresh();
227 $('#mainTabContainer').unblock();
228 var $otherPanel = $(chosen ? '#tagset-' + chosen : '#tree');
229 if ($('.tag-tree', $otherPanel).length) {
230 $('.tag-tree', $otherPanel).jstree(true).refresh();
237 function isDraggable(nodes, event) {
238 var draggable = true;
239 _.each(nodes, function(node) {
240 if (node.data.is_reserved && !CRM.checkPerm('administer reserved tags')) {
248 .append('<div class="tag-tree-wrapper"><div class="tag-tree"></div><div class="tag-info"></div></div>')
249 .on('change', 'input[type=color]', changeColor)
250 .on('change', 'input[name=used_for]', changeUsedFor)
251 .on('click', '.clear-tag-selection', clearSelection)
252 .on('click', '.move-tag-button', moveTagDialog)
253 .on('click', '.used-for-toggle', function() {
254 $(this).attr('style', 'display: none !important;').next().show();
256 .on('click', 'a.crm-clear-link', function() {
257 $('.tag-tree', $panel).jstree(true).refresh();
259 .on('crmPopupFormSuccess crmFormSuccess', function(e, cts, data) {
260 if ($(e.target).hasClass('tagset-action-delete')) {
262 } else if ($(e.target).hasClass('tagset-action-update')) {
263 updateTagset(data.tag);
265 $('.tag-tree', $panel).jstree(true).refresh();
269 plugins = ['wholerow', 'changed', 'search'];
271 // Allow drag-n-drop nesting of the tag tree
275 $('.tag-tree', $panel)
276 .on('changed.jstree loaded.jstree', changeSelection)
277 .on('move_node.jstree', moveTag)
278 .on('search.jstree', function() {
284 url: CRM.url('civicrm/ajax/tagTree'),
285 data: function(node) {
286 return {parent_id: node.id === '#' ? tagset : node.id};
289 themes: {icons: false},
294 url : CRM.url('civicrm/ajax/tagTree')
296 'show_only_matches': true
300 is_draggable: isDraggable,
305 $('input[name=filter_tag_tree]', $panel).on('keyup change', function(e) {
306 var element = $(this);
307 var searchString = element.val();
308 if (e.type == 'change') {
309 if (window.searchedString === searchString) {
310 if (searchString === '') {
311 $('.tag-tree', $panel).jstree("clear_search");
312 $('.tag-tree', $panel).jstree("refresh", true, true);
315 $('.tag-tree', $panel).block();
316 $(".tag-tree", $panel).jstree("search", searchString);
317 delete window.searchedString;
322 if (this.timer) clearTimeout(this.timer);
323 this.timer = setTimeout(function() {
324 if (_.isEmpty(window.searchedString) || window.searchedString !== searchString) {
325 window.searchedString = searchString;
326 element.trigger('change');
333 function newTagset() {
334 CRM.loadForm(CRM.url('civicrm/tag/edit', {action: 'add', tagset: 1}))
335 .on('crmFormSuccess', function(e, data) {
336 tagSets[data.tag.id] = data.tag;
337 tagSets[data.tag.id].display_name = user.display_name;
338 formatTagSet(tagSets[data.tag.id]);
339 $("#new-tagset").before('<div id="tagset-' + data.tag.id + '">');
340 $("a[href='#new-tagset']").parent().before('<li class="ui-corner-all crm-tab-button"><a href="#tagset-' + data.tag.id + '">' + data.tag.name + '</a></li>');
341 $('#mainTabContainer').tabs('refresh');
342 $('#mainTabContainer').tabs('option', 'active', -2);
346 $('#mainTabContainer')
348 .on("tabsbeforeactivate", function (event, ui) {
349 var id = $(ui.newPanel).attr('id');
350 if (id === 'new-tagset') {
351 event.preventDefault();
355 if ($.inArray(id, renderedTabs) < 0) {
356 renderedTabs.push(id);
357 renderTree(ui.newPanel);
361 renderTree($('#tree'));
363 // Prevent the info box from scrolling offscreen
364 $window.on('scroll resize', function () {
365 var $wrapper = $('.tag-tree-wrapper:visible'),
366 pos = $wrapper.offset(),
367 $box = $('.tag-info:visible');
368 if ($window.scrollTop() + menuHeight > pos.top) {
372 right: parseInt($window.width() - (pos.left + $wrapper.width())),
373 width: parseInt($wrapper.width() * .40)
376 $box.removeAttr('style');
383 <style type="text/css">
384 div.tag-tree-wrapper {
397 border: 1px solid #aaa;
399 box-shadow: 0 0 4px #e3e3e3;
401 box-sizing: border-box;
404 div.tag-info .clear-tag-selection {
411 div.tag-info .clear-tag-selection:hover,
412 div.tag-info .clear-tag-selection:active {
415 .tag-tree-wrapper .tag-tree a.crm-tag-item {
423 #tree a.crm-tag-item {
426 li.is-reserved > a:after {
429 {/literal}{if !call_user_func(array('CRM_Core_Permission', 'check'), 'administer reserved tags')}{literal}
430 #tree li.is-reserved > a.crm-tag-item {
433 li.is-reserved > a:after {
436 {/literal}{/if}{literal}
437 .tag-tree-wrapper ul {
441 div.tag-info h4 .crm-editable {
445 div.tag-info .crm-editable-enabled {
448 div.tag-info .crm-editable-enabled[data-field=description] {
451 div.tag-info input[type=color] {
454 div.tag-info input[disabled] {
464 div.tag-info .crm-submit-buttons {
470 <script type="text/template" id="noneSelectedTpl">
471 <% if (length) {ldelim} %>
472 <h4>{ts}None Selected{/ts}</h4>
474 <p>{ts}Select one or more tags for details.{/ts}</p>
475 <% {rdelim} else {ldelim} %>
476 <h4>{ts}Empty Tag Set{/ts}</h4>
478 <p>{ts}No tags have been created in this set.{/ts}</p>
480 <div class="crm-submit-buttons">
481 <a href="{crmURL p="civicrm/tag/edit" q="action=add&parent_id="}<%= tagset || '' %>" class="button crm-popup">
482 <span><i class="crm-i fa-plus"></i> {ts}Add Tag{/ts}</span>
484 <% if(tagset && adminTagsets) {ldelim} %>
485 <a href="{crmURL p="civicrm/tag/edit" q="action=update&id="}<%= tagset %>" class="button crm-popup tagset-action-update">
486 <span><i class="crm-i fa-pencil"></i> {ts}Edit Set{/ts}</span>
489 <% if(tagset && !length && adminTagsets && (!is_reserved || adminReserved)) {ldelim} %>
490 <a href="{crmURL p="civicrm/tag/edit" q="action=delete&id="}<%= tagset %>" class="button crm-popup small-popup tagset-action-delete">
491 <span><i class="crm-i fa-trash"></i> {ts}Delete Set{/ts}</span>
497 <script type="text/template" id="oneSelectedTpl">
498 <div class="crm-entity" data-entity="Tag" data-id="<%= id %>">
500 <input type="color" value="<%= data.color %>" <% if (!data.is_reserved || adminReserved) {ldelim} %>title="{ts}Select color{/ts}" <% {rdelim} else {ldelim} %>disabled<% {rdelim} %> />
501 <span class="<% if (!data.is_reserved || adminReserved) {ldelim} %>crm-editable<% {rdelim} %>" data-field="name"><%- text %></span>
504 <div><span class="tdl">{ts}Description:{/ts}</span>
505 <span class="<% if (!data.is_reserved || adminReserved) {ldelim} %>crm-editable<% {rdelim} %>" data-field="description"><%- data.description %></span>
507 <div><span class="tdl">{ts}Selectable:{/ts}</span>
508 <span class="<% if (!data.is_reserved || adminReserved) {ldelim} %>crm-editable<% {rdelim} %>" data-field="is_selectable" data-type="select"><% if (data.is_selectable) {ldelim} %> {ts}Yes{/ts} <% {rdelim} else {ldelim} %> {ts}No{/ts} <% {rdelim} %></span>
510 <div><span class="tdl">{ts}Reserved:{/ts}</span>
511 <span class="<% if (adminReserved) {ldelim} %>crm-editable<% {rdelim} %>" data-field="is_reserved" data-type="select"><% if (data.is_reserved) {ldelim} %> {ts}Yes{/ts} <% {rdelim} else {ldelim} %> {ts}No{/ts} <% {rdelim} %></span>
513 <% if (parent === '#' && !tagset) {ldelim} %>
515 <span class="tdl">{ts}Used For:{/ts}</span>
517 <span class="<% if (!data.is_reserved || adminReserved) { %>crm-editable-enabled used-for-toggle<% } %>">
518 <% if (!data.used_for.length) { %><i class="crm-i fa-pencil crm-editable-placeholder"></i><% } %>
519 <% _.forEach(data.used_for, function(key, i) { %><%- (i ? ', ' : '') + usedFor[key] %><% }) %>
521 <span style="display: none">
522 <% _.forEach(usedFor, function(label, key) { %>
523 <span style="white-space: nowrap">
524 <input type="checkbox" name="used_for" value="<%= key %>" id="<%= id + '_used_for_' + key %>" <% if (data.used_for.indexOf(key) > -1) { %>checked<% } %> />
525 <label for="<%= id + '_used_for_' + key %>"><%- label %></label>
532 <div><span class="tdl">{ts}Usage Count:{/ts}</span> <%= data.usages %></div>
533 <a class="clear-tag-selection" href="#" title="{ts}Clear selection{/ts}"><i class="crm-i fa-ban"></i></a>
535 <div class="crm-submit-buttons">
536 <% if(!tagset) {ldelim} %>
537 <a href="{crmURL p="civicrm/tag/edit" q="action=add&parent_id="}<%= id %>" class="button crm-popup" title="{ts}Create new tag under this one{/ts}">
538 <span><i class="crm-i fa-plus"></i> {ts}Add Child{/ts}</span>
541 <a href="{crmURL p="civicrm/tag/edit" q="action=add&clone_from="}<%= id %>" class="button crm-popup" title="{ts}Duplicate ths tag{/ts}">
542 <span><i class="crm-i fa-copy"></i> {ts}Clone Tag{/ts}</span>
544 <% if(!data.is_reserved || adminReserved) {ldelim} %>
545 <% if(tagsetCount) {ldelim} %>
546 <a href="#move" class="button move-tag-button" title="{ts}Move to a different tagset{/ts}">
547 <span><i class="crm-i fa-share-square-o"></i> {ts}Move Tag{/ts}</span>
550 <% if(!hasChildren) {ldelim} %>
551 <a href="{crmURL p="civicrm/tag/edit" q="action=delete&id="}<%= id %>" class="button crm-popup small-popup">
552 <span><i class="crm-i fa-trash"></i> {ts}Delete{/ts}</span>
559 <script type="text/template" id="moreSelectedTpl">
560 <h4>{ts 1="<%= items.length %>"}%1 Tags Selected{/ts}</h4>
562 <% if (reserved) {ldelim} %>
563 <p>* {ts 1="<%= reserved %>"}%1 reserved.{/ts}</p>
565 <p><span class="tdl">{ts}Total Usage:{/ts}</span> <%= usages %></p>
566 <a class="clear-tag-selection" href="#" title="{ts}Clear selection{/ts}"><i class="crm-i fa-ban"></i></a>
567 <div class="crm-submit-buttons">
568 <% if(!reserved || adminReserved) {ldelim} %>
569 <a href="{crmURL p="civicrm/tag/merge" q="id="}<%= items.join() %>" class="button crm-popup small-popup" title="{ts}Combine tags into one{/ts}">
570 <span><i class="crm-i fa-compress"></i> {ts}Merge Tags{/ts}</span>
572 <% if(tagsetCount) {ldelim} %>
573 <a href="#move" class="button move-tag-button" title="{ts}Move to a different tagset{/ts}">
574 <span><i class="crm-i fa-share-square-o"></i> {ts}Move Tags{/ts}</span>
577 <% if(!hasChildren) {ldelim} %>
578 <a href="{crmURL p="civicrm/tag/edit" q="action=delete&id="}<%= items.join() %>" class="button crm-popup small-popup">
579 <span><i class="crm-i fa-trash"></i> {ts}Delete All{/ts}</span>
586 <script type="text/template" id="tagsetHeaderTpl">
587 <div class="tagset-header">
589 <% if(is_reserved == 1) {ldelim} %><strong>{ts}Reserved{/ts}</strong><% {rdelim} %>
590 <% if(undefined === display_name) {ldelim} var display_name = null; {rdelim} %>
591 {ts 1="<%= used_for_label.join(', ') %>" 2="<%= date %>" 3="<%= display_name %>"}Tag Set for %1 (created %2 by %3).{/ts}
592 <% if(typeof description === 'string' && description.length) {ldelim} %><p><em><%- description %></em></p><% {rdelim} %>
594 <input class="crm-form-text big" name="filter_tag_tree" placeholder="{ts}Filter List{/ts}" allowclear="1"/>
595 <a class="crm-hover-button crm-clear-link" style="visibility:hidden;" title="{ts}Clear{/ts}"><i class="crm-i fa-times"></i></a>