Skip to content

Instantly share code, notes, and snippets.

@jaredhoyt
Created November 8, 2010 15:43
Show Gist options
  • Save jaredhoyt/667826 to your computer and use it in GitHub Desktop.
Save jaredhoyt/667826 to your computer and use it in GitHub Desktop.
<?php
class FileTreeHelper extends AppHelper {
var $elements = array(
'tree' => '<ul class="tree">%s</ul>',
'folder' => '<li id="tree-folder_%d" class="folder open">
<div class="folder-name-wrap"><span class="icon">Sort</span><span class="folder-name">%s</span></div>
<div class="folder-contents"><ul class="folders">%s</ul><ul class="files">%s</ul></div></li>',
'file' => '<li id="tree-file_%d" class="file"><span class="icon">Sort</span><span class="file-name">%s</span></li>'
);
var $settings = array();
public function generate($folders, $settings = array()) {
if (!is_array($folders)) {
return false;
}
$this->settings = array_merge(
array(
'folders' => 'Folder',
'files' => 'File'
), $settings
);
$out = '';
foreach ($folders as $folder) {
$out .= $this->parseFolder($folder);
}
return sprintf($this->elements['tree'], $out);
}
private function parseFolder($folder) {
$folders = '';
if (!empty($folder['children'])) {
foreach ($folder['children'] as $subfolder) {
$folders .= $this->parseFolder($subfolder);
}
}
$files = '';
if (!empty($folder[$this->settings['files']])) {
foreach ($folder[$this->settings['files']] as $file) {
$files .= sprintf($this->elements['file'], $file['id'], $file['name']);
}
}
return sprintf($this->elements['folder'], $folder[$this->settings['folders']]['id'], $folder[$this->settings['folders']]['name'], $folders, $files);
}
}
?>
var productTree = {
// Check for modified items before proceeding
confirmChanges : function(){
if ($('#active-item').find('input.modified, textarea.modified').length) {
return confirm('You have unsaved changes. Are you sure you want to continue?');
}
return true;
},
// Strip non-numeric characters from id
extractId : function(element){
return $(element).attr('id').replace(/[^0-9]*/, '');
},
// Focus (and select) active item name input
focusActiveItem : function(select){
$('#active-item input.name')[0].focus();
if (select === true) {
$('#active-item input.name')[0].select();
}
},
// Load item for editing when selected
loadItem : function(url, data, callback){
// Center ajax loader
$('#active-item div.content-wrap').addClass('loading');
$('#active-item div.ajax-loader div.message').css({
left : $('#active-item div.ajax-loader').width()/2 - 77,
top : $('#active-item div.ajax-loader').height()/2 - 29
});
if (typeof data == 'function') {
callback = data;
data = new Object();
}
$('#active-item div.content').load(url, data, function(){
$('#active-item div.content-wrap').removeClass('loading');
callback();
});
},
// Save folder order below node
saveFolderOrder : function(node){
var folders = new Object();
var instance = this;
$(node).children('div.folder-contents').children('ul.folders').children().each(function(index){
folders[index] = {
ProductFolder: {
id: instance.extractId(this),
sort: index
}
}
});
$.post('/admin/product_folders/reorder/' + productTree.extractId(node), { data : folders });
},
// Save folder order below node
saveFileOrder : function(node){
var files = new Object();
var instance = this;
$(node).children().each(function(index){
files[index] = {
Product: {
id: instance.extractId(this),
sort: index
}
}
});
$.post('/admin/products/reorder', { data : files });
},
// Load edit view into active item
selectItem : function(item, callback) {
if (!this.confirmChanges() || $(item).parent().hasClass('ui-prevent-select')) {
$(item).parent().removeClass('ui-prevent-select');
return false;
}
$('ul.tree .selected').removeClass('selected');
$('#product-tree a.create-product').addClass('disabled');
if ($(item).length) {
$(item).addClass('selected');
$('#product-tree a.create-product').removeClass('disabled');
callback = callback || this.focusActiveItem;
if ($(item).hasClass('file')) {
this.loadItem('/admin/products/edit/' + this.extractId(item), callback);
$(item).parents('li.folder').addClass('open');
} else {
this.loadItem('/admin/product_folders/edit/' + this.extractId($(item).parent()), callback);
}
}
}
};
jQuery(function(){
// Filter : type
$('#FilterType').change(function(){
window.location = '/admin/products/tree/' + $(this).val();
});
// Filter : autocomplete
$('#FilterSearch').autocomplete({
source: function(request, response) {
$.ajax({
url : '/admin/products/search/' + productTree.extractId($('ul.tree li.folder:first')),
dataType : "json",
data : request,
success : function(data){
if (data.length) {
response(data);
} else {
$('#FilterSearch').removeClass('searching');
}
}
});
},
select: function(event, ui){
productTree.selectItem($('#tree-file_' + ui.item.val));
},
search: function(){
$('#FilterSearch').addClass('searching');
},
close: function(){
$('#FilterSearch').removeClass('searching').val('');
}
});
// Sorting folders and files
$('ul.tree').delegate('div.folder-contents', 'mouseover', function(){
$(this).children('ul.folders').sortable({
axis: "y",
handle: "> div.folder-name-wrap span.icon",
items: "> li.folder",
start: function(event, ui){
$(ui.item).addClass('ui-prevent-select');
},
update: function(event, ui){
productTree.saveFolderOrder($(ui.item).parents('li.folder:first'));
}
})
$(this).children('ul.files').sortable({
axis: "y",
handle: "> span.icon",
items: "> li.file",
start: function(event, ui){
$(ui.item).addClass('ui-prevent-select');
},
update: function(event, ui){
productTree.saveFileOrder($(ui.item).parent());
}
});
});
// Dragging folders and files
$('ul.tree ul.folders').delegate('li.folder', 'mouseover', function(event){
$(this).draggable({
handle: "> div.folder-name-wrap span.folder-name",
revert: true,
revertDuration: 150,
scope: "tree",
zIndex: 2
});
});
// Dropping items onto folders
$('ul.tree').delegate('div.folder-contents', 'mouseover', function(){
$('div.folder-name-wrap').droppable({
greedy: true,
scope: "tree",
tolerance: "pointer",
drop: function(event, ui){
var element = $(ui.helper);
var target = $(event.target);
if (target.hasClass('ui-folder-hover')) {
if (element.hasClass('folder')) {
element.appendTo(target.next('div.folder-contents').children('ul.folders'));
} else {
element.appendTo(target.next('div.folder-contents').children('ul.files'));
}
target.removeClass('ui-folder-hover').parent().addClass('open');
var targetId = productTree.extractId(target.parent());
if (element.hasClass('file')) {
$.post('/admin/products/edit/' + productTree.extractId(element), { data : { Product : { product_folder_id : targetId }}});
} else {
$.post('/admin/product_folders/edit/' + productTree.extractId(element), { data : { ProductFolder : { parent_id : targetId }}});
}
}
},
over: function(event, ui){
var element = $(ui.helper);
var target = $(event.target);
var targetDepth = target.parents('div.folder-contents').length;
var validTarget = false;
if (element.hasClass('file') && targetDepth > 0) {
validTarget = true;
} else if (element.hasClass('folder')) {
validTarget = (targetDepth == 0 || (targetDepth == 1 && element.find('div.folder-contents').length == 1));
}
if (validTarget) {
target.addClass('ui-folder-hover');
}
},
out: function(event, ui){
$(event.target).removeClass('ui-folder-hover');
}
});
});
// Toggling folders
$('ul.tree').delegate('div.folder-name-wrap', 'dblclick', function(){
if ($(this).parents('div.folder-contents').length) {
$(this).parent().toggleClass('open');
}
}).disableSelection();
// Selecting a tree item
$('div.folder-contents').delegate('div.folder-name-wrap, li.file', 'click', function(){
productTree.selectItem(this);
return false;
}).disableSelection();
// Editing items with save/cancel buttons
$('#active-item').delegate('div.submit a', 'click', function(event){
if ($(this).hasClass('save')) {
var name = $(this).parents('form:first').find('input.name').val();
var selector = $('ul.tree .selected').hasClass('file') ? 'file-name' : 'folder-name';
if ($('#ProductFolderImage').val()) {
$('#ItemEditForm').attr('target', 'image-submit')[0].submit();
}
productTree.loadItem($('#ItemEditForm').attr('action'), $('#ItemEditForm').serializeArray(), function(){
if ($('#active-item div.content').find('p.success').length) {
$('ul.tree .selected span.' + selector + ':first').text(name);
productTree.selectItem(null);
}
});
} else if ($(this).hasClass('cancel')) {
productTree.loadItem('/admin/products #active-item div.content > *', function(){
productTree.selectItem(null);
});
} else if ($(this).hasClass('delete')) {
if ($('ul.tree .selected').hasClass('file')) {
var message = 'Are you sure you want to delete this product?';
var item = $('ul.tree .selected');
} else {
var message = 'Are you sure you want to delete this folder and all of its contents?';
var item = $('ul.tree .selected').parent();
}
if (confirm(message)) {
productTree.loadItem(this.href, function(){
item.fadeOut(function(){
$(this).remove();
productTree.selectItem(null);
});
});
}
}
event.preventDefault();
}).delegate('input, textarea', 'change', function(){
$(this).addClass('modified');
}).delegate('form', 'submit', function(event){
$(this).find('a.save').click();
event.preventDefault();
}).delegate('div.image-options a.view-image', 'click', function(event){
magicbox.load(this.href, { title: 'Product Folder Image' });
event.preventDefault();
}).delegate('div.image-options a.delete-image', 'click', function(event){
if (confirm('Are you sure you want to delete this image?')) {
productTree.loadItem(this.href);
}
event.preventDefault();
});
// Create new folder
$('a.create-folder').click(function(event){
var parent = $('ul.tree li.folder:first');
$.get('/admin/product_folders/create/' + productTree.extractId(parent), function(folder){
productTree.selectItem(parent.children('div.folder-contents').children('ul.folders').prepend(folder).find('div.folder-name-wrap:first'), function(){
productTree.saveFolderOrder(parent);
productTree.focusActiveItem(true);
});
});
event.preventDefault();
});
// Create new product
$('a.create-product').click(function(event){
if (!$(this).hasClass('disabled')) {
var parent = $('ul.tree .selected').parents('li.folder:first');
$.get('/admin/products/create/' + productTree.extractId(parent), function(product){
productTree.selectItem(parent.children('div.folder-contents').children('ul.files').append(product).find('li.file:last'), function(){
productTree.saveFileOrder(parent.children('div.folder-contents').children('ul.files'));
productTree.focusActiveItem(true);
});
});
}
event.preventDefault();
});
});
<?php echo $this->FileTree->generate($folders, array('folders' => 'ProductFolder', 'files' => 'Product')); ?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment