18 | * @license http://www.opensource.org/licenses/BSD-3-Clause New BSD license
19 | * @version 2.6
20 | */
21 | (function($, tokenize){
22 |
23 | // Keycodes
24 | var KEYS = {
25 | BACKSPACE: 8,
26 | TAB: 9,
27 | ENTER: 13,
28 | ESCAPE: 27,
29 | ARROW_UP: 38,
30 | ARROW_DOWN: 40
31 | };
32 |
33 | // Debounce timeout
34 | var debounce_timeout = null;
35 |
36 | // Data storage constant
37 | var DATA = 'tokenize';
38 |
39 | /**
40 | * Get Tokenize object
41 | *
42 | * @param {Object} options
43 | * @param {jQuery} el
44 | * @returns {$.tokenize}
45 | */
46 | var getObject = function(options, el){
47 |
48 | if(!el.data(DATA)){
49 | var obj = new $.tokenize($.extend({}, $.fn.tokenize.defaults, options));
50 | el.data(DATA, obj);
51 | obj.init(el);
52 | }
53 |
54 | return el.data(DATA);
55 |
56 | };
57 |
58 | /**
59 | * Tokenize constructor
60 | *
61 | * @param {Object} opts
62 | */
63 | $.tokenize = function(opts){
64 |
65 | if(opts == undefined){
66 | opts = $.fn.tokenize.defaults;
67 | }
68 |
69 | this.options = opts;
70 | };
71 |
72 | $.extend($.tokenize.prototype, {
73 |
74 | /**
75 | * Init tokenize object
76 | *
77 | * @param {jQuery} el jQuery object of the select
78 | */
79 | init: function(el){
80 |
81 | var $this = this;
82 | this.select = el.attr('multiple', 'multiple').css({margin: 0, padding: 0, border: 0}).hide();
83 |
84 | this.container = $('')
85 | .attr('class', this.select.attr('class'))
86 | .addClass('Tokenize');
87 |
88 | if(this.options.maxElements == 1){
89 | this.container.addClass('OnlyOne');
90 | }
91 |
92 | this.dropdown = $('')
93 | .addClass('Dropdown');
94 |
95 | this.tokensContainer = $('')
96 | .addClass('TokensContainer');
97 |
98 | if(this.options.autosize){
99 | this.tokensContainer
100 | .addClass('Autosize');
101 | }
102 |
103 | this.searchToken = $('')
104 | .addClass('TokenSearch')
105 | .appendTo(this.tokensContainer);
106 |
107 | this.searchInput = $('')
108 | .appendTo(this.searchToken);
109 |
110 | if(this.options.searchMaxLength > 0){
111 | this.searchInput.attr('maxlength', this.options.searchMaxLength)
112 | }
113 |
114 | if(this.select.prop('disabled')){
115 | this.disable();
116 | }
117 |
118 | if(this.options.sortable){
119 | if (typeof $.ui != 'undefined'){
120 | this.tokensContainer.sortable({
121 | items: 'li.Token',
122 | cursor: 'move',
123 | placeholder: 'Token MovingShadow',
124 | forcePlaceholderSize: true,
125 | update: function(){
126 | $this.updateOrder();
127 | },
128 | start: function(){
129 | $this.searchToken.hide();
130 | },
131 | stop: function(){
132 | $this.searchToken.show();
133 | }
134 | }).disableSelection();
135 | } else {
136 | this.options.sortable = false;
137 | console.error('jQuery UI is not loaded, sortable option has been disabled');
138 | }
139 | }
140 |
141 | this.container
142 | .append(this.tokensContainer)
143 | .append(this.dropdown)
144 | .insertAfter(this.select);
145 |
146 | this.tokensContainer.on('click', function(e){
147 | e.stopImmediatePropagation();
148 | $this.searchInput.get(0).focus();
149 | $this.updatePlaceholder();
150 | if($this.dropdown.is(':hidden') && $this.searchInput.val() != ''){
151 | $this.search();
152 | }
153 | });
154 |
155 | this.searchInput.on('blur', function(){
156 | $this.tokensContainer.removeClass('Focused');
157 | });
158 |
159 | this.searchInput.on('focus click', function(){
160 | $this.tokensContainer.addClass('Focused');
161 | if($this.options.displayDropdownOnFocus){
162 | $this.search();
163 | }
164 | });
165 |
166 | this.searchInput.on('keydown', function(e){
167 | $this.resizeSearchInput();
168 | $this.keydown(e);
169 | });
170 |
171 | this.searchInput.on('keyup', function(e){
172 | $this.keyup(e);
173 | });
174 |
175 | this.searchInput.on('keypress', function(e){
176 | $this.keypress(e);
177 | });
178 |
179 | this.searchInput.on('paste', function(){
180 | setTimeout(function(){ $this.resizeSearchInput(); }, 10);
181 | setTimeout(function(){
182 | var paste_elements = [];
183 | if(Array.isArray($this.options.delimiter)){
184 | paste_elements = $this.searchInput.val().split(new RegExp($this.options.delimiter.join('|'), 'g'));
185 | } else {
186 | paste_elements = $this.searchInput.val().split($this.options.delimiter);
187 | }
188 | if(paste_elements.length > 1){
189 | $.each(paste_elements, function(_, value){
190 | $this.tokenAdd(value.trim(), '');
191 | });
192 | }
193 | }, 20);
194 | });
195 |
196 | $(document).on('click', function(){
197 | $this.dropdownHide();
198 | if($this.options.maxElements == 1){
199 | if($this.searchInput.val()){
200 | $this.tokenAdd($this.searchInput.val(), '');
201 | }
202 | }
203 | });
204 |
205 | this.resizeSearchInput();
206 | this.remap(true);
207 | this.updatePlaceholder();
208 |
209 | },
210 |
211 | /**
212 | * Update elements order in the select html element
213 | */
214 | updateOrder: function(){
215 |
216 | if(this.options.sortable){
217 | var previous, current, $this = this;
218 | $.each(this.tokensContainer.sortable('toArray', {attribute: 'data-value'}), function(k, v){
219 | current = $('option[value="' + v + '"]', $this.select);
220 | if(previous == undefined){
221 | current.prependTo($this.select);
222 | } else {
223 | previous.after(current);
224 | }
225 | previous = current;
226 | });
227 |
228 | this.options.onReorder(this);
229 | }
230 |
231 | },
232 |
233 | /**
234 | * Update placeholder visibility
235 | */
236 | updatePlaceholder: function(){
237 |
238 | if(this.options.placeholder){
239 | if(this.placeholder == undefined){
240 | this.placeholder = $('').addClass('Placeholder').html(this.options.placeholder);
241 | this.placeholder.insertBefore($('li:first-child', this.tokensContainer));
242 | }
243 |
244 | if(this.searchInput.val().length == 0 && $('li.Token', this.tokensContainer).length == 0){
245 | this.placeholder.show();
246 | } else {
247 | this.placeholder.hide();
248 | }
249 | }
250 |
251 | },
252 |
253 | /**
254 | * Display the dropdown
255 | */
256 | dropdownShow: function(){
257 |
258 | this.dropdown.show();
259 | this.options.onDropdownShow(this);
260 |
261 | },
262 |
263 | /**
264 | * Move the focus on the dropdown previous element
265 | */
266 | dropdownPrev: function(){
267 |
268 | if($('li.Hover', this.dropdown).length > 0){
269 | if(!$('li.Hover', this.dropdown).is('li:first-child')){
270 | $('li.Hover', this.dropdown).removeClass('Hover').prev().addClass('Hover');
271 | } else {
272 | $('li.Hover', this.dropdown).removeClass('Hover');
273 | $('li:last-child', this.dropdown).addClass('Hover');
274 | }
275 | } else {
276 | $('li:first', this.dropdown).addClass('Hover');
277 | }
278 |
279 | },
280 |
281 | /**
282 | * Move the focus on the dropdown next element
283 | */
284 | dropdownNext: function(){
285 |
286 | if($('li.Hover', this.dropdown).length > 0){
287 | if(!$('li.Hover', this.dropdown).is('li:last-child')){
288 | $('li.Hover', this.dropdown).removeClass('Hover').next().addClass('Hover');
289 | } else {
290 | $('li.Hover', this.dropdown).removeClass('Hover');
291 | $('li:first-child', this.dropdown).addClass('Hover');
292 | }
293 | } else {
294 | $('li:first', this.dropdown).addClass('Hover');
295 | }
296 |
297 | },
298 |
299 | /**
300 | * Add an item to the dropdown
301 | *
302 | * @param {string} value The value of the item
303 | * @param {string} text The display text of the item
304 | * @param {string|undefined} [html] The html display text of the item (override previous parameter)
305 | * @return {$.tokenize}
306 | */
307 | dropdownAddItem: function(value, text, html){
308 |
309 | html = html || text;
310 |
311 | if(!$('li[data-value="' + value + '"]', this.tokensContainer).length){
312 | var $this = this;
313 | var item = $('')
314 | .attr('data-value', value)
315 | .attr('data-text', text)
316 | .html(html)
317 | .on('click', function(e){
318 | e.stopImmediatePropagation();
319 | $this.tokenAdd($(this).attr('data-value'), $(this).attr('data-text'));
320 | }).on('mouseover', function(){
321 | $(this).addClass('Hover');
322 | }).on('mouseout', function(){
323 | $('li', $this.dropdown).removeClass('Hover');
324 | });
325 |
326 | this.dropdown.append(item);
327 | this.options.onDropdownAddItem(value, text, html, this);
328 | }
329 |
330 | return this;
331 |
332 | },
333 |
334 | /**
335 | * Hide dropdown
336 | */
337 | dropdownHide: function(){
338 |
339 | this.dropdownReset();
340 | this.dropdown.hide();
341 |
342 | },
343 |
344 | /**
345 | * Reset dropdown
346 | */
347 | dropdownReset: function(){
348 |
349 | this.dropdown.html('');
350 |
351 | },
352 |
353 | /**
354 | * Resize search input according the value length
355 | */
356 | resizeSearchInput: function(){
357 |
358 | this.searchInput.attr('size', Number(this.searchInput.val().length)+5);
359 | this.updatePlaceholder();
360 |
361 | },
362 |
363 | /**
364 | * Reset search input
365 | */
366 | resetSearchInput: function(){
367 |
368 | this.searchInput.val("");
369 | this.resizeSearchInput();
370 |
371 | },
372 |
373 | /**
374 | * Reset pending tokens
375 | */
376 | resetPendingTokens: function(){
377 |
378 | $('li.PendingDelete', this.tokensContainer).removeClass('PendingDelete');
379 |
380 | },
381 |
382 | /**
383 | * Keypress
384 | *
385 | * @param {object} e
386 | */
387 | keypress: function(e){
388 |
389 | var delimiter = false;
390 |
391 | if(Array.isArray(this.options.delimiter)){
392 | if(this.options.delimiter.indexOf(String.fromCharCode(e.which)) >= 0){
393 | delimiter = true;
394 | }
395 | } else {
396 | if(String.fromCharCode(e.which) == this.options.delimiter){
397 | delimiter = true;
398 | }
399 | }
400 |
401 | if(delimiter){
402 | e.preventDefault();
403 | this.tokenAdd(this.searchInput.val(), '');
404 | }
405 |
406 | },
407 |
408 | /**
409 | * Keydown
410 | *
411 | * @param {object} e
412 | */
413 | keydown: function(e){
414 |
415 | switch(e.keyCode){
416 | case KEYS.BACKSPACE:
417 | if(this.searchInput.val().length == 0){
418 | e.preventDefault();
419 | if($('li.Token.PendingDelete', this.tokensContainer).length){
420 | this.tokenRemove($('li.Token.PendingDelete').attr('data-value'));
421 | } else {
422 | $('li.Token:last', this.tokensContainer).addClass('PendingDelete');
423 | }
424 | this.dropdownHide();
425 | }
426 | break;
427 |
428 | case KEYS.TAB:
429 | case KEYS.ENTER:
430 | if($('li.Hover', this.dropdown).length){
431 | var element = $('li.Hover', this.dropdown);
432 | e.preventDefault();
433 | this.tokenAdd(element.attr('data-value'), element.attr('data-text'));
434 | } else {
435 | if(this.searchInput.val()){
436 | e.preventDefault();
437 | this.tokenAdd(this.searchInput.val(), '');
438 | }
439 | }
440 | this.resetPendingTokens();
441 | break;
442 |
443 | case KEYS.ESCAPE:
444 | this.resetSearchInput();
445 | this.dropdownHide();
446 | this.resetPendingTokens();
447 | break;
448 |
449 | case KEYS.ARROW_UP:
450 | e.preventDefault();
451 | this.dropdownPrev();
452 | break;
453 |
454 | case KEYS.ARROW_DOWN:
455 | e.preventDefault();
456 | this.dropdownNext();
457 | break;
458 |
459 | default:
460 | this.resetPendingTokens();
461 | break;
462 | }
463 |
464 | },
465 |
466 | /**
467 | * Keyup
468 | *
469 | * @param {object} e
470 | */
471 | keyup: function(e){
472 |
473 | this.updatePlaceholder();
474 | switch(e.keyCode){
475 | case KEYS.TAB:
476 | case KEYS.ENTER:
477 | case KEYS.ESCAPE:
478 | case KEYS.ARROW_UP:
479 | case KEYS.ARROW_DOWN:
480 | break;
481 |
482 | case KEYS.BACKSPACE:
483 | if(this.searchInput.val()){
484 | this.search();
485 | } else {
486 | this.dropdownHide();
487 | }
488 | break;
489 | default:
490 | if(this.searchInput.val()){
491 | this.search();
492 | }
493 | break;
494 | }
495 |
496 | },
497 |
498 | /**
499 | * Search an element in the select or using ajax
500 | */
501 | search: function(){
502 |
503 | var $this = this;
504 | var count = 1;
505 |
506 | if((this.options.maxElements > 0 && $('li.Token', this.tokensContainer).length >= this.options.maxElements) ||
507 | this.searchInput.val().length < this.options.searchMinLength){
508 | return false;
509 | }
510 |
511 | if(this.options.datas == 'select'){
512 |
513 | var found = false, regexp = new RegExp(this.searchInput.val().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"), 'i');
514 | this.dropdownReset();
515 |
516 | $('option', this.select).not(':selected, :disabled').each(function(){
517 | if(count <= $this.options.nbDropdownElements){
518 | if(regexp.test($(this).html())){
519 | $this.dropdownAddItem($(this).attr('value'), $(this).html());
520 | found = true;
521 | count++;
522 | }
523 | } else {
524 | return false;
525 | }
526 | });
527 |
528 | if(found){
529 | $('li:first', this.dropdown).addClass('Hover');
530 | this.dropdownShow();
531 | } else {
532 | this.dropdownHide();
533 | }
534 |
535 | } else {
536 |
537 | this.debounce(function(){
538 | if(this.ajax !== undefined){
539 | this.ajax.abort();
540 | }
541 | this.ajax = $.ajax({
542 | url: $this.options.datas,
543 | data: $this.options.searchParam + "=" + encodeURIComponent($this.searchInput.val()),
544 | dataType: $this.options.dataType,
545 | success: function(data){
546 | if(data){
547 | $this.dropdownReset();
548 | $.each(data, function(key, val){
549 | if(count <= $this.options.nbDropdownElements){
550 | var html;
551 | if(val[$this.options.htmlField]){
552 | html = val[$this.options.htmlField];
553 | }
554 | $this.dropdownAddItem(val[$this.options.valueField], val[$this.options.textField], html);
555 | count++;
556 | } else {
557 | return false;
558 | }
559 | });
560 | if($('li', $this.dropdown).length){
561 | $('li:first', $this.dropdown).addClass('Hover');
562 | $this.dropdownShow();
563 | return true;
564 | }
565 | }
566 | $this.dropdownHide();
567 | },
568 | error: function(xhr, text_status) {
569 | $this.options.onAjaxError($this, xhr, text_status);
570 | }
571 | });
572 | }, this.options.debounce);
573 |
574 | }
575 |
576 | },
577 |
578 | /**
579 | * Debounce method for ajax request
580 | * @param {function} func
581 | * @param {number} threshold
582 | */
583 | debounce: function(func, threshold){
584 |
585 | var obj = this, args = arguments;
586 | var delayed = function(){
587 | func.apply(obj, args);
588 | debounce_timeout = null;
589 | };
590 | if(debounce_timeout){
591 | clearTimeout(debounce_timeout);
592 | }
593 | debounce_timeout = setTimeout(delayed, threshold || this.options.debounce);
594 |
595 | },
596 |
597 | /**
598 | * Add a token in container
599 | *
600 | * @param {string} value The value of the token
601 | * @param {string|undefined} [text] The label of the token (use value if empty)
602 | * @param {boolean|undefined} [first] If true, onAddToken event will be not called
603 | * @return {$.tokenize}
604 | */
605 | tokenAdd: function(value, text, first){
606 |
607 | value = this.escape(value).trim();
608 |
609 | if(value == undefined || value == ''){
610 | return this;
611 | }
612 |
613 | text = text || value;
614 | first = first || false;
615 |
616 | if(this.options.maxElements > 0 && $('li.Token', this.tokensContainer).length >= this.options.maxElements){
617 | this.resetSearchInput();
618 | return this;
619 | }
620 |
621 | var $this = this;
622 | var close_btn = $('')
623 | .addClass('Close')
624 | .html("×")
625 | .on('click', function(e){
626 | e.stopImmediatePropagation();
627 | $this.tokenRemove(value);
628 | });
629 |
630 | if($('option[value="' + value + '"]', this.select).length){
631 | if(!first && ($('option[value="' + value + '"]', this.select).attr('selected') === true ||
632 | $('option[value="' + value + '"]', this.select).prop('selected') === true)){
633 | this.options.onDuplicateToken(value, text, this);
634 | }
635 | $('option[value="' + value + '"]', this.select).attr('selected', true).prop('selected', true);
636 | } else if(this.options.newElements || (!this.options.newElements && $('li[data-value="' + value + '"]', this.dropdown).length > 0)) {
637 | var option = $('')
638 | .attr('selected', true)
639 | .attr('value', value)
640 | .attr('data-type', 'custom')
641 | .prop('selected', true)
642 | .html(text);
643 | this.select.append(option);
644 | } else {
645 | this.resetSearchInput();
646 | return this;
647 | }
648 |
649 | if($('li.Token[data-value="' + value + '"]', this.tokensContainer).length > 0) {
650 | return this;
651 | }
652 |
653 | $('')
654 | .addClass('Token')
655 | .attr('data-value', value)
656 | .append('' + text + '')
657 | .prepend(close_btn)
658 | .insertBefore(this.searchToken);
659 |
660 | if(!first){
661 | this.options.onAddToken(value, text, this);
662 | }
663 |
664 | this.resetSearchInput();
665 | this.dropdownHide();
666 | this.updateOrder();
667 |
668 | return this;
669 |
670 | },
671 |
672 | /**
673 | * Remove a token
674 | *
675 | * @param {string} value The value of the token who has to be removed
676 | * @returns {$.tokenize}
677 | */
678 | tokenRemove: function(value){
679 |
680 | var option = $('option[value="' + value + '"]', this.select);
681 |
682 | if(option.attr('data-type') == 'custom'){
683 | option.remove();
684 | } else {
685 | option.removeAttr('selected').prop('selected', false);
686 | }
687 |
688 | $('li.Token[data-value="' + value + '"]', this.tokensContainer).remove();
689 |
690 | this.options.onRemoveToken(value, this);
691 | this.resizeSearchInput();
692 | this.dropdownHide();
693 | this.updateOrder();
694 |
695 | return this;
696 |
697 | },
698 |
699 | /**
700 | * Clear tokens
701 | *
702 | * @returns {$.tokenize}
703 | */
704 | clear: function(){
705 |
706 | var $this = this;
707 |
708 | $('li.Token', this.tokensContainer).each(function(){
709 | $this.tokenRemove($(this).attr('data-value'));
710 | });
711 |
712 | this.options.onClear(this);
713 | this.dropdownHide();
714 |
715 | return this;
716 |
717 | },
718 |
719 | /**
720 | * Disable tokenize
721 | *
722 | * @returns {$.tokenize}
723 | */
724 | disable: function(){
725 |
726 | this.select.prop('disabled', true);
727 | this.searchInput.prop('disabled', true);
728 | this.container.addClass('Disabled');
729 | if(this.options.sortable){
730 | this.tokensContainer.sortable('disable');
731 | }
732 |
733 | return this;
734 |
735 | },
736 |
737 | /**
738 | * Enable tokenize
739 | *
740 | * @returns {$.tokenize}
741 | */
742 | enable: function(){
743 |
744 | this.select.prop('disabled', false);
745 | this.searchInput.prop('disabled', false);
746 | this.container.removeClass('Disabled');
747 | if(this.options.sortable){
748 | this.tokensContainer.sortable('enable');
749 | }
750 |
751 | return this;
752 |
753 | },
754 |
755 | /**
756 | * Refresh tokens reflecting select options
757 | *
758 | * @param {boolean} first If true, onAddToken event will be not called
759 | * @returns {$.tokenize}
760 | */
761 | remap: function(first){
762 |
763 | var $this = this;
764 | var tmp = $("option:selected", this.select);
765 |
766 | first = first || false;
767 |
768 | this.clear();
769 |
770 | tmp.each(function(){
771 | $this.tokenAdd($(this).val(), $(this).html(), first);
772 | });
773 |
774 | return this;
775 |
776 | },
777 |
778 | /**
779 | * Retrieve tokens value to an array
780 | *
781 | * @returns {Array}
782 | */
783 | toArray: function(){
784 |
785 | var output = [];
786 | $("option:selected", this.select).each(function(){
787 | output.push($(this).val());
788 | });
789 | return output;
790 |
791 | },
792 |
793 | /**
794 | * Escape string
795 | *
796 | * @param {string} string
797 | * @returns {string}
798 | */
799 | escape: function(string){
800 |
801 | var tmp = document.createElement("div");
802 | tmp.innerHTML = string;
803 | string = tmp.textContent || tmp.innerText || "";
804 |
805 | return String(string).replace(/["]/g, function(){
806 | return '';
807 | });
808 |
809 | }
810 |
811 | });
812 |
813 | /**
814 | * Tokenize plugin
815 | *
816 | * @param {Object|undefined} [options]
817 | * @returns {$.tokenize|Array}
818 | */
819 | $.fn.tokenize = function(options){
820 |
821 | options = options || {};
822 |
823 | var selector = this.filter('select');
824 |
825 | if(selector.length > 1){
826 | var objects = [];
827 | selector.each(function(){
828 | objects.push(getObject(options, $(this)));
829 | });
830 | return objects;
831 | }
832 | else
833 | {
834 | return getObject(options, $(this));
835 | }
836 | };
837 |
838 | $.fn.tokenize.defaults = {
839 |
840 | datas: 'select',
841 | placeholder: false,
842 | searchParam: 'search',
843 | searchMaxLength: 0,
844 | searchMinLength: 0,
845 | debounce: 0,
846 | delimiter: ',',
847 | newElements: true,
848 | autosize: false,
849 | nbDropdownElements: 10,
850 | displayDropdownOnFocus: false,
851 | maxElements: 0,
852 | sortable: false,
853 | dataType: 'json',
854 | valueField: 'value',
855 | textField: 'text',
856 | htmlField: 'html',
857 |
858 | onAddToken: function(value, text, e){},
859 | onRemoveToken: function(value, e){},
860 | onClear: function(e){},
861 | onReorder: function(e){},
862 | onDropdownAddItem: function(value, text, html, e){},
863 | onDropdownShow: function(e){},
864 | onDuplicateToken: function(value, text, e){},
865 | onAjaxError: function(e, xhr, text_status){}
866 |
867 | };
868 |
869 | })(jQuery, 'tokenize');
870 |
--------------------------------------------------------------------------------
/assets/tvs/selector/ajax.php:
--------------------------------------------------------------------------------
1 | db->connect();
14 | if (empty ($modx->config)) {
15 | $modx->getSettings();
16 | }
17 | if(!isset($_SESSION['mgrValidated'])){
18 | die();
19 | }
20 | $modx->invokeEvent('OnManagerPageInit',array('invokedBy'=>'Selector','tvId'=>(int)$_REQUEST['tvid'],'tvName'=>$_REQUEST['tvname']));
21 |
22 | $mode = (isset($_REQUEST['mode']) && is_scalar($_REQUEST['mode'])) ? $_REQUEST['mode'] : null;
23 | $out = null;
24 |
25 | $controllerClass = isset($_REQUEST['tvname']) ? $_REQUEST['tvname'] : '';
26 | $controllerClass = preg_replace('/[^A-Za-z_]/', '', $controllerClass);
27 |
28 | if (!class_exists('\\Selector\\'.ucfirst($controllerClass.'Controller'), false)) {
29 | if (file_exists(MODX_BASE_PATH.'assets/tvs/selector/lib/'.$controllerClass.'.controller.class.php')) {
30 | require_once (MODX_BASE_PATH.'assets/tvs/selector/lib/'.$controllerClass.'.controller.class.php');
31 | $controllerClass = '\\Selector\\'.ucfirst($controllerClass.'Controller');
32 | } else {
33 | require_once (MODX_BASE_PATH . 'assets/tvs/selector/lib/controller.class.php');
34 | $controllerClass = '\\Selector\\SelectorController';
35 | }
36 | }
37 |
38 | $controller = new $controllerClass($modx);
39 | if($controller instanceof \Selector\SelectorController){
40 | if (!empty($mode) && method_exists($controller, $mode)) {
41 | $out = call_user_func_array(array($controller, $mode), array());
42 | }else{
43 | $out = call_user_func_array(array($controller, 'listing'), array());
44 | }
45 | }
46 |
47 | echo ($out = is_array($out) ? json_encode($out) : $out);
48 |
--------------------------------------------------------------------------------
/assets/tvs/selector/css/style.css:
--------------------------------------------------------------------------------
1 | div.Tokenize {
2 | display: block;
3 | }
4 | div.Tokenize li:before { /* @FIX: MODX.Evo.Custom 1.2.1-d9.1.4 MODxRE2 DropdownMenu theme CSS */
5 | content: '';
6 | }
7 | div.Tokenize ul.TokensContainer {
8 | height:auto;
9 | width:100%;
10 | }
11 | div.Tokenize ul.Dropdown {
12 | width:auto;
13 | border: 1px solid #d4d4d4;
14 | }
15 | div.Tokenize ul.TokensContainer {
16 | border: 1px solid #d4d4d4;
17 | }
18 | div.Tokenize ul.TokensContainer li {
19 | list-style: none!important;
20 | }
21 | div.Tokenize ul.TokensContainer li.Token {
22 | cursor: move;
23 | cursor: -webkit-grab;
24 | border-color: #d4d4d4;
25 | background-color: #f3f3f3;
26 | }
27 | div.Tokenize ul.TokensContainer li.Token, div.Tokenize ul.TokensContainer li.TokenSearch {
28 | padding: 0;
29 | height: auto;
30 | border-radius: 2px;
31 | }
32 | div.Tokenize ul.TokensContainer li a {
33 | text-decoration: none!important;
34 | }
35 | div.Tokenize ul.TokensContainer li.Token a.Close {
36 | margin: 0;
37 | padding: 3px 6px;
38 | cursor: pointer;
39 | color: #999;
40 | background: rgba(0,0,0,0.05);
41 | border-right: 1px solid rgba(0,0,0,0.07);
42 | font-weight: bold;
43 | float: left;
44 | }
45 | div.TokenizeMeasure, div.Tokenize ul li span, div.Tokenize ul.TokensContainer li.TokenSearch input {
46 | display: inline-block;
47 | font-family: inherit;
48 | font-size: 0.8125rem;
49 | padding: 3px 8px;
50 | }
51 |
52 | .darkness div.Tokenize ul.TokensContainer, .darkness div.Tokenize ul.Dropdown {
53 | background: #202329;
54 | border-color: #414449;
55 | }
56 |
57 | .darkness div.Tokenize ul.TokensContainer li.Token {
58 | border-color: #414449;
59 | background-color: #282c34;
60 | color: #bbb;
61 | }
62 |
63 | .darkness div.Tokenize ul.TokensContainer li.Token a.Close {
64 | background: rgba(0, 0, 0, 0.13);
65 | border-right: 1px solid rgba(255, 255, 255, 0.08);
66 | }
67 |
68 | .darkness div.Tokenize ul.TokensContainer li.TokenSearch input {
69 | color: #bbb;
70 | }
71 |
--------------------------------------------------------------------------------
/assets/tvs/selector/css/styles.json:
--------------------------------------------------------------------------------
1 | {
2 | "styles": {
3 | "TokenizeCss" : {
4 | "version":"2.6.1",
5 | "src":"assets/js/tokenize/jquery.tokenize.css"
6 | },
7 | "SelectorCss" : {
8 | "version":"1.4.1",
9 | "src":"assets/tvs/selector/css/style.css"
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/assets/tvs/selector/js/scripts.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "Sortable":{
4 | "version":"1.2.0",
5 | "src":"assets/js/sortable/Sortable.min.js"
6 | },
7 | "Tokenize" : {
8 | "version":"2.6.1",
9 | "src":"assets/js/tokenize/jquery.tokenize.js"
10 | },
11 | "Selector" : {
12 | "version":"1.4.1",
13 | "src":"assets/tvs/selector/js/selector.js"
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/assets/tvs/selector/js/selector.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Pathologic on 13.06.2015.
3 | */
4 | (function($){
5 | selector = {
6 | update: function(container, target, delimeter) {
7 | var tvvalue = [];
8 | $('option:selected', container).each(function(){
9 | tvvalue.push($(this).attr('value'));
10 | });
11 | target.val(tvvalue.join(delimeter));
12 | },
13 | sort: function(container, target, delimeter) {
14 | select = container.first();
15 | select.empty();
16 | $('ul.TokensContainer li.Token',container).each(function() {
17 | var value = $(this).data('value');
18 | var text = $('span',this).html();
19 | var option = $('')
20 | .attr('selected', 'selected')
21 | .attr('value', value)
22 | .html(text);
23 | select.append(option);
24 |
25 | }
26 | );
27 | this.update(select,target, delimeter);
28 | }
29 | }
30 | $('body').attr('ondragstart','');
31 | })(jQuery)
--------------------------------------------------------------------------------
/assets/tvs/selector/lib/controller.class.php:
--------------------------------------------------------------------------------
1 | 'id,pagetitle,parent,html,text',
15 | 'JSONformat' => 'old',
16 | 'display' => 10,
17 | 'offset' => 0,
18 | 'sortBy' => 'c.id',
19 | 'sortDir' => 'desc',
20 | 'parents' => 0,
21 | 'showParent' => 1,
22 | 'depth' => 10,
23 | 'searchContentFields' => 'c.id,c.pagetitle,c.longtitle',
24 | 'searchTVFields' => '',
25 | 'idField' => 'id',
26 | 'textField' => 'pagetitle',
27 | 'addWhereList' => 'c.published = 1',
28 | 'prepare' => 'Selector\SelectorController::prepare'
29 | );
30 | public $dlParamsNoSearch = array();
31 |
32 | /**
33 | * SelectorController constructor.
34 | * @param \DocumentParser $modx
35 | */
36 | public function __construct(\DocumentParser $modx)
37 | {
38 | $this->modx = $modx;
39 | }
40 |
41 | /**
42 | * @param array $data
43 | * @param \DocumentParser $modx
44 | * @param \DocLister $_DL
45 | * @param \prepare_DL_Extender $_extDocLister
46 | * @return array
47 | */
48 | public static function prepare(
49 | array $data,
50 | \DocumentParser $modx,
51 | \DocLister $_DL,
52 | \prepare_DL_Extender $_extDocLister
53 | ) {
54 | if (($docCrumbs = $_extDocLister->getStore('currentParents' . $data['parent'])) === null) {
55 | $modx->documentObject['id'] = $data['id'];
56 | $docCrumbs = rtrim($modx->runSnippet('DLcrumbs', array(
57 | 'ownerTPL' => '@CODE:[+crumbs.wrap+]',
58 | 'tpl' => '@CODE: [+title+] /',
59 | 'tplCurrent' => '@CODE: [+title+] /',
60 | 'hideMain' => '1'
61 | )), ' /');
62 | $_extDocLister->setStore('currentParents' . $data['parent'], $docCrumbs);
63 | }
64 | if ($search = $_DL->getCFGDef('search')) {
65 | $html = preg_replace("/(" . preg_quote($search, "/") . ")/iu", "$0",
66 | $data[$_DL->getCFGDef('textField', 'pagetitle')]);
67 | } else {
68 | $html = $data[$_DL->getCFGDef('textField', 'pagetitle')];
69 | }
70 | $data['text'] = "{$data[$_DL->getCFGDef('idField','id')]}. {$data[$_DL->getCFGDef('textField','pagetitle')]}";
71 | $data['html'] = "{$docCrumbs}
{$data['id']}. {$html}
";
72 |
73 | return $data;
74 | }
75 |
76 | /**
77 | * @return string
78 | */
79 | public function listing()
80 | {
81 | $search = isset($_REQUEST['search']) && is_scalar($_REQUEST['search']) ? $_REQUEST['search'] : '';
82 | if (!empty($search)) {
83 | if (substr($search, 0, 1) == '=') {
84 | $search = substr($search, 1);
85 | $mode = '=';
86 | } else {
87 | $mode = 'like';
88 | }
89 | $this->dlParams['search'] = $search;
90 | $searchContentFields = explode(',', $this->dlParams['searchContentFields']);
91 | $filters = array();
92 |
93 | if (is_numeric($search)) {
94 | $filters[] = "content:id:=:{$search}";
95 | }
96 |
97 | foreach ($searchContentFields as $field) {
98 | $filters[] = "content:{$field}:{$mode}:{$search}";
99 | }
100 |
101 | if (!empty($this->dlParams['searchTVFields'])) {
102 | $searchTVFields = explode(',', $this->dlParams['searchTVFields']);
103 | foreach ($searchTVFields as $tv) {
104 | $filters[] = "tv:{$tv}:{$mode}:{$search}";
105 | }
106 | }
107 | $filters = implode(';', $filters);
108 | if (!empty($filters)) {
109 | $filters = "OR({$filters})";
110 | $this->dlParams['filters'] = $filters;
111 | }
112 | }
113 |
114 | return $this->modx->runSnippet("DocLister", $this->dlParams);
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/assets/tvs/selector/lib/selector.class.php:
--------------------------------------------------------------------------------
1 | 0,
17 | 'nbDropdownElements' => 10,
18 | 'searchMaxLength' => 30,
19 | 'searchMinLength' => 0,
20 | 'textField' => 'text',
21 | 'valueField' => 'id',
22 | 'htmlField' => 'html',
23 | 'divider' => ',',
24 | 'tokenConfig' => array(
25 | 'tpl' => '@CODE: '
26 | )
27 | );
28 | public $tpl = 'assets/tvs/selector/tpl/selector.tpl';
29 | public $jsListDefault = 'assets/tvs/selector/js/scripts.json';
30 | public $jsListCustom = 'assets/tvs/selector/js/custom.json';
31 | public $cssListDefault = 'assets/tvs/selector/css/styles.json';
32 | public $cssListCustom = 'assets/tvs/selector/css/custom.json';
33 |
34 | /**
35 | * Selector constructor.
36 | * @param \DocumentParser $modx
37 | * @param array $tv
38 | * @param array $documentData
39 | */
40 | public function __construct(\DocumentParser $modx, array $tv, array $documentData)
41 | {
42 | $this->modx = $modx;
43 | $this->tv = $tv;
44 | $this->documentData = $documentData;
45 | $this->DLTemplate = \DLTemplate::getInstance($this->modx);
46 | $this->fs = \Helpers\FS::getInstance();
47 | $this->loadConfig($tv['name']);
48 | }
49 |
50 | /**
51 | * @return bool|string
52 | */
53 | public function prerender()
54 | {
55 | $output = '';
56 | $plugins = $this->modx->pluginEvent;
57 | if ((array_search('ManagerManager',
58 | $plugins['OnDocFormRender']) === false) && !isset($this->modx->loadedjscripts['jQuery'])
59 | ) {
60 | $output .= '';
61 | $this->modx->loadedjscripts['jQuery'] = array('version' => '1.9.1');
62 | $output .= '';
63 | }
64 | $tpl = MODX_BASE_PATH . $this->tpl;
65 | if ($this->fs->checkFile($tpl)) {
66 | $output .= '[+js+][+styles+]' . file_get_contents($tpl);
67 | } else {
68 | $this->modx->logEvent(0, 3, "Cannot load {$this->tpl} .", $this->customTvName);
69 |
70 | return false;
71 | }
72 |
73 | return $output;
74 | }
75 |
76 | /**
77 | * @param $list
78 | * @param array $ph
79 | * @return string
80 | */
81 | public function renderJS($list, $ph = array())
82 | {
83 | $js = '';
84 | $scripts = MODX_BASE_PATH . $list;
85 | if ($this->fs->checkFile($scripts)) {
86 | $scripts = @file_get_contents($scripts);
87 | $scripts = $this->DLTemplate->parseChunk('@CODE:' . $scripts, $ph);
88 | $scripts = json_decode($scripts, true);
89 | $scripts = isset($scripts['scripts']) ? $scripts['scripts'] : $scripts['styles'];
90 | foreach ($scripts as $name => $params) {
91 | if (!isset($this->modx->loadedjscripts[$name])) {
92 | if ($this->fs->checkFile($params['src'])) {
93 | $this->modx->loadedjscripts[$name] = array('version' => $params['version']);
94 | $val = explode('.', $params['src']);
95 | if (end($val) == 'js') {
96 | $js .= '';
97 | } else {
98 | $js .= '';
99 | }
100 | } else {
101 | $this->modx->logEvent(0, 3, 'Cannot load ' . $params['src'], $this->customTvName);
102 | }
103 | }
104 | }
105 | } else {
106 | if ($list == $this->jsListDefault) {
107 | $this->modx->logEvent(0, 3, "Cannot load {$this->jsListDefault} .", $this->customTvName);
108 | } elseif ($list == $this->cssListDefault) {
109 | $this->modx->logEvent(0, 3, "Cannot load {$this->cssListDefault} .", $this->customTvName);
110 | }
111 | }
112 |
113 | return $js;
114 | }
115 |
116 | /**
117 | * @return array
118 | */
119 | public function getTplPlaceholders()
120 | {
121 | $ph = array(
122 | 'tv_id' => $this->tv['id'],
123 | 'tv_value' => $this->tv['value'],
124 | 'tv_name' => $this->tv['name'],
125 | 'doc_id' => $this->documentData['id'],
126 | 'doc_parent' => $this->documentData['parent'],
127 | 'doc_template' => $this->documentData['template'],
128 | 'site_url' => $this->modx->config['site_url'],
129 | 'timestamp' => $this->getTimestamp(),
130 | 'values' => !empty($this->tv['value']) ? $this->modx->runSnippet('DocLister',
131 | array_merge($this->config['tokenConfig'], array(
132 | 'idType' => 'documents',
133 | 'documents' => str_replace($this->config['divider'], ',', $this->tv['value']),
134 | 'showNoPublish' => 1,
135 | 'sortType' => 'doclist'
136 | ))
137 | ) : ''
138 | );
139 | unset($this->config['tokenConfig']);
140 |
141 | return array_merge($ph, $this->config);
142 | }
143 |
144 | /**
145 | * @return string
146 | */
147 | public function render()
148 | {
149 | $output = $this->prerender();
150 | if ($output !== false) {
151 | $ph = $this->getTplPlaceholders();
152 | $ph['js'] = $this->renderJS($this->jsListDefault, $ph) . $this->renderJS($this->jsListCustom, $ph);
153 | $ph['styles'] = $this->renderJS($this->cssListDefault, $ph) . $this->renderJS($this->cssListCustom, $ph);
154 | $output = $this->DLTemplate->parseChunk('@CODE:' . $output, $ph);
155 | }
156 |
157 | return $output;
158 | }
159 |
160 | /**
161 | * @param $config
162 | */
163 | protected function loadConfig($config)
164 | {
165 | if (empty($config)) {
166 | return;
167 | }
168 | $file = MODX_BASE_PATH . "assets/tvs/selector/config/{$config}.php";
169 | if ($this->fs->checkFile($file)) {
170 | $_config = include($file);
171 | if (is_array($_config)) {
172 | $this->config = array_merge($this->config, $_config);
173 | }
174 | }
175 | }
176 |
177 | protected function getTimestamp()
178 | {
179 | $cachePath = defined('EVO_CORE_PATH') ? $this->modx->getSiteCacheFilePath() : MODX_BASE_PATH . $this->modx->getCacheFolder() . 'siteCache.idx.php';
180 |
181 | return filemtime($cachePath);
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/assets/tvs/selector/selector.customtv.php:
--------------------------------------------------------------------------------
1 | ERROR:Please use the MODx Content Manager instead of accessing this file directly.
');
11 | }
12 | global $content;
13 |
14 | include_once(MODX_BASE_PATH.'assets/tvs/selector/lib/selector.class.php');
15 | $documentData = array(
16 | 'id' => isset($content['id']) ? (int)$content['id'] : 0,
17 | 'template' => (int)$content['template'],
18 | 'parent' => (int)$content['parent'],
19 | );
20 |
21 | $selector = new \Selector\Selector (
22 | $modx,
23 | $row,
24 | $documentData
25 | );
26 |
27 | echo $selector->render();
28 |
--------------------------------------------------------------------------------
/assets/tvs/selector/tpl/selector.tpl:
--------------------------------------------------------------------------------
1 |
2 |
5 |
40 |
--------------------------------------------------------------------------------