diff --git a/css/TreeDropdownField.css b/css/TreeDropdownField.css index 23981f2f9..49ce0edb0 100644 --- a/css/TreeDropdownField.css +++ b/css/TreeDropdownField.css @@ -1,78 +1,43 @@ div.TreeDropdownField { - width: 35em; - padding: 0; -} -html>body div.TreeDropdownField { - position:relative; -} - -div.TreeDropdownField { - width: 27.7em; - background: #fff; - font-size: 12px; + width: 200px; height: 21px; } div.TreeDropdownField span.items { + float: left; + overflow:hidden; + width: 174px; height: 14px; border: 1px #7f9db9 solid; cursor: pointer; - width: 25.4em; - float: left; - padding: 2px 0 2.5px 4px; + padding: 2px 0 2px 4px; background-color: #fff; - margin:0; - font-size: 12px; - overflow:hidden; } -div.TreeDropdownField div.tree_holder { - clear: left; +div.TreeDropdownField .panel { + position:relative; + overflow: auto; + display: none; + z-index: 1000; cursor: default; border: 1px black solid; - margin: 0; + width: 200px; height: 200px; - overflow: auto; background-color: #fff; - /** - * HACK IE6, see http://www.hedgerwow.com/360/bugs/css-select-free.html - */ - position:absolute; - z-index:10; - width:33em;/*must have for any value*/; } -div.TreeDropdownField div.tree_holder ul.tree a { +div.TreeDropdownField .panel.loading { + background: #f00; +} + +div.TreeDropdownField .panel ul.tree a { font-size: 12px; } -div.TreeDropdownField div.tree_holder ul.tree { +div.TreeDropdownField .panel ul.tree { margin-top: 0; } -html>body div.TreeDropdownField div.tree_holder { - top: 20px; - left: 0px; - z-index: 1000; -} - -/** - * HACK IE6, see http://www.hedgerwow.com/360/bugs/css-select-free.html - */ -div.TreeDropdownField div.tree_holder iframe { - display:none;/* IE5*/ - display/**/:block;/* IE5*/ - position:absolute; - border-style: none; - top:0; - left:0; - z-index:-1; - filter:mask(); - width:31em;/*must have for any big value*/ - height:200px/*must have for any big value*/; - border: none; -} - div.TreeDropdownField a.editLink { border:none; text-decoration: none; diff --git a/forms/TreeDropdownField.php b/forms/TreeDropdownField.php index 8423b59aa..605df3597 100755 --- a/forms/TreeDropdownField.php +++ b/forms/TreeDropdownField.php @@ -115,16 +115,13 @@ class TreeDropdownField extends FormField { public function Field() { Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang'); - Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/prototype/prototype.js'); - Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/behaviour/behaviour.js'); Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery/jquery.js'); Requirements::javascript(SAPPHIRE_DIR . '/javascript/jquery_improvements.js'); - Requirements::javascript(SAPPHIRE_DIR . '/javascript/tree/tree.js'); - // needed for errorMessage() - Requirements::javascript(SAPPHIRE_DIR . '/javascript/LeftAndMain.js'); - Requirements::javascript(SAPPHIRE_DIR . '/javascript/TreeSelectorField.js'); + Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-entwine/dist/jquery.entwine-dist.js'); + Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jstree/jquery.jstree.js'); + Requirements::javascript(SAPPHIRE_DIR . '/javascript/TreeDropdownField.js'); - Requirements::css(SAPPHIRE_DIR . '/javascript/tree/tree.css'); + Requirements::css(SAPPHIRE_DIR . '/thirdparty/jquery-ui-themes/smoothness/jquery.ui.all.css'); Requirements::css(SAPPHIRE_DIR . '/css/TreeDropdownField.css'); if($this->Value() && $record = $this->objectForKey($this->Value())) { @@ -138,7 +135,7 @@ class TreeDropdownField extends FormField { array ( 'id' => "TreeDropdownField_{$this->id()}", 'class' => 'TreeDropdownField single' . ($this->extraClass() ? " {$this->extraClass()}" : ''), - 'href' => $this->form ? $this->Link() : "", + 'href' => $this->form ? $this->Link('tree') : "", ), $this->createTag ( 'input', @@ -152,7 +149,7 @@ class TreeDropdownField extends FormField { $this->createTag( 'input', array( - 'class' => 'items', + 'class' => 'title', 'value' => '(Choose or type search)' ) ) : @@ -197,7 +194,8 @@ class TreeDropdownField extends FormField { $this->search = Convert::Raw2SQL($request->getVar('search')); - if($ID = (int) $request->latestparam('ID')) { + $ID = (is_numeric($request->latestparam('ID'))) ? (int)$request->latestparam('ID') : (int)$request->requestVar('ID'); + if($ID) { $obj = DataObject::get_by_id($this->sourceObject, $ID); $isSubTree = true; @@ -232,15 +230,15 @@ class TreeDropdownField extends FormField { $obj->markToExpose($this->objectForKey($value)); } } - - $eval = '"
  • Name() . '-{$child->' . $this->keyField . '}\" class=\"$child->class"' . + + $eval = '"
  • Name() . '-{$child->' . $this->keyField . '}\" data-id=\"$child->' . $this->keyField . '\" class=\"$child->class"' . ' . $child->markingClasses() . "\">ID\">" . $child->' . $this->labelField . ' . ""'; if($isSubTree) { return substr(trim($obj->getChildrenAsUL('', $eval, null, true)), 4, -5); + } else { + return $obj->getChildrenAsUL('class="tree"', $eval, null, true); } - - return $obj->getChildrenAsUL('class="tree"', $eval, null, true); } /** diff --git a/javascript/TreeDropdownField.js b/javascript/TreeDropdownField.js index da0042b1a..8e52ff84d 100644 --- a/javascript/TreeDropdownField.js +++ b/javascript/TreeDropdownField.js @@ -1,40 +1,22 @@ (function($) { $.entwine('ss', function($){ - var strings = { - 'openlink': 'Open', - 'fieldTitle': '(choose)', - 'searchFieldTitle': '(choose or search)' - }; - /** - * @todo Error display - * @todo No results display for search - * @todo Automatic expansion of ajax children when multiselect is triggered - * @todo Automatic panel positioning based on available space (top/bottom) - * @todo forceValue - * @todo Automatic width - * @todo Expand title height to fit all elements + * @todo Locale support + * @todo Multiselect + * @todo Search */ $('.TreeDropdownField').entwine({ onmatch: function() { - this.append( - '' + - '' + - '
    ' - ); - if(this.data('title')) this.setTitle(this.data('title')); - this.getPanel().hide(); - - this._super(); + this.append('
    '); }, getPanel: function() { return this.find('.panel'); }, openPanel: function() { - var panel = this.getPanel(), tree = this.find('.tree-holder'); + var panel = this.getPanel(); panel.show(); - if(tree.is(':empty')) this.loadTree(); + if(!panel.find('li').length) this.loadTree(); }, closePanel: function() { this.getPanel().hide(); @@ -43,55 +25,40 @@ this[this.getPanel().is(':visible') ? 'closePanel' : 'openPanel'](); }, setTitle: function(title) { - if(!title) title = strings.fieldTitle; - this.find('.title').text(title); - this.data('title', title); // separate view from storage (important for search cancellation) }, getTitle: function() { return this.find('.title').text(); }, setValue: function(val) { - this.find(':input:hidden').val(val); + this.find(':input').val(val); }, getValue: function() { - return this.find(':input:hidden').val(); + return this.find(':input').val(); }, - loadTree: function(params, callback) { - var self = this, panel = this.getPanel(), treeHolder = $(panel).find('.tree-holder'); - var params = (params) ? this.getRequestParams().concat(params) : this.getRequestParams(); - panel.addClass('loading'); - treeHolder.load(this.data('url-tree'), params, function(html, status, xhr) { - var firstLoad = true; + loadTree: function() { + var self = this, treeHolder = $(this.getPanel()).find('.tree-holder'); + this.addClass('loading'); + treeHolder.load(this.attr('href'), {}, function(html, status, xhr) { if(status == 'success') { $(this) .bind('loaded.jstree', function(e, data) { var val = self.getValue(); if(val) data.inst.select_node(treeHolder.find('*[data-id=' + val + ']')); - firstLoad = false; - if(callback) callback.apply(self); }) .jstree(self.getTreeConfig()) .bind('select_node.jstree', function(e, data) { - var node = data.rslt.obj, id = $(node).data('id'); - if(self.getValue() == id) { - self.setValue(null); - self.setTitle(null); - } else { - self.setValue(id); - self.setTitle(data.inst.get_text(node)); - } - - // Avoid auto-closing panel on first load - if(!firstLoad) self.closePanel(); + var node = data.rslt.obj; + self.setValue($(node).data('id')); + self.setTitle(data.inst.get_text(node)); + self.closePanel(); }); } - panel.removeClass('loading'); + self.removeClass('loading'); }); }, getTreeConfig: function() { - var self = this; return { 'core': { 'initially_open': ['record-0'], @@ -101,11 +68,9 @@ // TODO Hack to avoid ajax load on init, see http://code.google.com/p/jstree/issues/detail?id=911 'data': this.getPanel().find('.tree-holder').html(), 'ajax': { - 'url': this.data('url-tree'), + 'url': this.attr('href'), 'data': function(node) { - var id = $(node).data("id") ? $(node).data("id") : 0, params = self.getRequestParams(); - params = params.concat([{name: 'ID', value: id}, {name: 'ajax', value: 1}]); - return params; + return { ID : $(node).data("id") ? $(node).data("id") : 0 , ajax: 1}; } } }, @@ -113,20 +78,11 @@ "select_limit" : 1, 'initially_select': [this.getPanel().find('.current').attr('id')] }, + 'themes': { + 'theme': 'apple' + }, 'plugins': ['html_data', 'ui', 'themes'] - // 'plugins': ['html_data', 'ui', 'themeroller'] }; - }, - /** - * If the field is contained in a form, submit all form parameters by default. - * This is useful to keep state like locale values which are typically - * encoded in hidden fields through the form. - * - * @return {array} - */ - getRequestParams: function() { - var form = this.parents('form'); - return form.length ? form.serializeArray() : []; } }); $('.TreeDropdownField *').entwine({ @@ -134,103 +90,9 @@ return this.parents('.TreeDropdownField:first'); } }); - $('.TreeDropdownField .toggle-panel-link, .TreeDropdownField span.title').entwine({ + $('.TreeDropdownField .editLink').entwine({ onclick: function(e) { this.getField().togglePanel(); - return false; - } - }); - - $('.TreeDropdownField.searchable').entwine({ - onmatch: function() { - this._super(); - - var title = this.data('title'); - this.find('.title').replaceWith( - $('') - ); - this.setTitle(title ? title : strings.searchFieldTitle); - }, - setTitle: function(title) { - if(!title) title = strings.fieldTitle; - - this.find('.title').val(title); - }, - getTitle: function() { - return this.find('.title').val(); - }, - search: function(str, callback) { - this.openPanel(); - this.loadTree({search: str}, callback); - }, - cancelSearch: function() { - this.closePanel(); - this.loadTree(); - this.setTitle(this.data('title')); - } - }); - - $('.TreeDropdownField.searchable input.search').entwine({ - onkeydown: function(e) { - var field = this.getField(); - if(e.keyCode == 13) { - // trigger search on ENTER key - field.search(this.val()); - return false; - } else if(e.keyCode == 27) { - // cancel search on ESC key - field.cancelSearch(); - } - } - }); - - $('.TreeDropdownField.multiple').entwine({ - getTreeConfig: function() { - var cfg = this._super(); - cfg.checkbox = {override_ui: true}; - cfg.plugins.push('checkbox'); - cfg.ui.select_limit = -1; - return cfg; - }, - loadTree: function(params, callback) { - var self = this, panel = this.getPanel(), treeHolder = $(panel).find('.tree-holder'); - var params = (params) ? this.getRequestParams().concat(params) : this.getRequestParams(); - panel.addClass('loading'); - treeHolder.load(this.data('url-tree'), params, function(html, status, xhr) { - var firstLoad = true; - if(status == 'success') { - $(this) - .bind('loaded.jstree', function(e, data) { - $.each(self.getValue(), function(i, val) { - data.inst.check_node(treeHolder.find('*[data-id=' + val + ']')); - }); - firstLoad = false; - if(callback) callback.apply(self); - }) - .jstree(self.getTreeConfig()) - .bind('uncheck_node.jstree check_node.jstree', function(e, data) { - var nodes = data.inst.get_checked(null, true); - self.setValue($.map(nodes, function(el, i) { - return $(el).data('id'); - })); - self.setTitle($.map(nodes, function(el, i) { - return data.inst.get_text(el); - })); - }); - } - - panel.removeClass('loading'); - }); - }, - getValue: function() { - var val = this._super(); - return val.split(/ *, */); - }, - setValue: function(val) { - this._super($.isArray(val) ? val.join(',') : val); - }, - setTitle: function(title) { - this._super($.isArray(title) ? title.join(', ') : title); } }); }); diff --git a/javascript/TreeSelectorField.js b/javascript/TreeSelectorField.js deleted file mode 100755 index 6a4403ead..000000000 --- a/javascript/TreeSelectorField.js +++ /dev/null @@ -1,438 +0,0 @@ -/** - * TreeDropdownField.js - */ -TreeDropdownField = Class.extend('Observable'); -TreeDropdownField.prototype = { - initialize: function() { - // Hook up all the fieldy bits - this.editLink = this.getElementsByTagName('a')[0]; - if (this.getElementsByTagName('span').length > 0) { - // no search, humanItems is a span - this.humanItems = this.getElementsByTagName('span')[0]; - this.inputTag = this.getElementsByTagName('input')[0]; - } - else { - // search is present, humanItems is an input - this.inputTag = this.getElementsByTagName('input')[0]; - this.humanItems = this.getElementsByTagName('input')[1]; - this.humanItems.onkeyup = this.search_onkeyup; - } - this.editLink.treeDropdownField = this; - this.humanItems.treeDropdownField = this; - this.inputTag.treeDropdownField = this; - - this.editLink.onclick = this.edit_click; - this.humanItems.onclick = this.human_click; - this.inputTag.setValue = this.setValue.bind(this); - }, - - destroy: function() { - if(this.editLink) { - this.editLink.onclick = null; - this.editLink.treeDropdownField = null; - this.editLink = null; - } - if(this.humanItems) { - this.humanItems.onclick = null; - this.humanItems.treeDropdownField = null; - this.humanItems = null; - } - if(this.inputTag) { - this.inputTag.setValue = null; - this.inputTag.treeDropdownField = null; - this.inputTag = null; - } - }, - - getName: function() { - return this.inputTag.name; - }, - - refresh: function() { - this.createTreeNode(); - - this.ajaxGetTree( (function(response) { - this.newTreeReady(response, false); - this.updateTreeLabel(); - }).bind(this)); - }, - - // Build a URL from the field's base URL and the given sub URL - buildURL: function(subURL) { - var baseURL = jQuery(this).attr('href'); - if (!baseURL) { - // Occurs if treedropdown has no form e.g. treefields in widget areas. - baseURL = this.ownerForm().action + '/field/' + this.getName() + '/'; - var baseTags = document.getElementsByTagName('base'); - var base = (baseTags) ? baseTags[0].href : ''; - if (base == baseURL.substring(0, base.length)) - baseURL = baseURL.substring(base.length); - } - var subHasQuerystring = subURL.match(/\?/); - - if(baseURL.match(/^(.*)\?(.*)$/)) { - if(subHasQuerystring) return RegExp.$1 + '/' + subURL + '&' + RegExp.$2 - else return RegExp.$1 + '/' + subURL + '?' + RegExp.$2 - } else { - return baseURL + '/' + subURL; - } - }, - ownerForm: function() { - var f =this.parentNode; - while(f && f.tagName.toLowerCase() != 'form') f = f.parentNode; - return f; - }, - - toggleTree: function() { - if(this.treeShown) this.hideTree(); - else this.showTree(); - }, - - createTreeNode: function(keepTreeHidden) { - if(!this.itemTree) { - this.itemTree = document.createElement('div'); - - if(keepTreeHidden) { - this.hideTree(); - } - - this.itemTree.className = 'tree_holder'; - this.itemTree.innerHTML = ss.i18n._t('LOADING', 'Loading...'); - this.appendChild(this.itemTree); - } - }, - - deleteTreeNode: function() { - if (!this.itemTree) return; - var parent = this.itemTree.parentNode; - parent.removeChild(this.itemTree); - this.itemTree = null; - }, - - showTree: function () { - if (!this.treeShown) this.saveCurrentState(); - this.treeShown = true; - - if(this.itemTree) { - this.itemTree.style.display = 'block'; - // Store this in a parameter so that stopObserving works - this.bound_testForBlur = this.testForBlur.bind(this); - Event.observe(document, 'click', this.bound_testForBlur); - this.stretchIframeIfNeeded(); - } else { - this.createTreeNode(); - - this.ajaxGetTree( (function(response) { - this.newTreeReady(response, false); - this.updateTreeLabel(); - }).bind(this)); - } - }, - - saveCurrentState: function() { - this.origHumanText = this.getHumanText(); - }, - - restoreOriginalState: function() { - this.setHumanText(this.origHumanText); - }, - - /** - * If this control is inside an iframe, stretch the iframe out to fit the tree. - */ - stretchIframeIfNeeded: function() { - if(parent && parent.document) { - if(!this.iframeObj) { - var iframes = parent.document.getElementsByTagName('iframe') - var i,item; - for(i=0;item=iframes[i];i++) { - if(item.contentWindow == window) { - this.iframeObj = item; - break; - } - } - } - - // This iframe stretching doesn't work with the greybox - if(this.iframeObj && this.iframeObj.id == 'GB_frame') return; - - var desiredHeight = Position.cumulativeOffset(this.itemTree)[1] + this.itemTree.offsetHeight + 2; - if(this.iframeObj && desiredHeight > this.iframeObj.offsetHeight) { - this.iframeObj.oldHeight = this.iframeObj.offsetHeight; - this.iframeObj.style.height = desiredHeight + 'px'; - } - } - }, - - unstretchIframeIfNeeded: function() { - if(this.iframeObj && this.iframeObj.oldHeight) - this.iframeObj.style.height = this.iframeObj.oldHeight + 'px'; - }, - - testForBlur: function (event) { - var clicked = Event.element(event); - if(clicked != this.itemTree && !hasAncestor(clicked, this.itemTree) && clicked != this.editLink && clicked != this.humanItems) { - this.hideTree(); - } - }, - - hideTree: function() { - this.treeShown = false; - if(this.itemTree) { - this.itemTree.style.display = 'none'; - if(this.bound_testForBlur) Event.stopObserving(document, 'click', this.bound_testForBlur); - // this.editLink.style.display = this.humanItems.style.display = 'block'; - this.unstretchIframeIfNeeded(); - } - // this.style.position = ''; - }, - - ajaxGetTree: function(after) { - var ajaxURL = this.buildURL('tree?forceValue=' + this.inputTag.value); - var secId = jQuery(':input[name=SecurityID]'); - ajaxURL += secId.length ? '&SecurityID=' + secId.val() : ''; - var localeField = jQuery(this.ownerForm()).find(':input[name=locale],:input[name=Locale]'); - if(localeField.length) {ajaxURL += "&locale=" + localeField.val();} - if(this.inputTag.value) ajaxURL += '&forceValue=' + this.inputTag.value; - if(this.search() != null) ajaxURL += "&search=" + this.search(); - jQuery.ajax({ - 'url': ajaxURL, - 'method' : 'get', - 'success' : after, - 'error' : function(response) { errorMessage("Error getting data", response); } - }); - }, - - search: function() { - if (this.humanItems.nodeName != 'INPUT' || !this.searched) return null; - return this.humanItems.value; - }, - - /** - * Called once the tree has been delivered from ajax - */ - newTreeReady: function (response, keepTreeHidden) { - this.itemTree.innerHTML = response.responseText; - // HACK IE6: see http://www.hedgerwow.com/360/bugs/css-select-free.html - this.itemTree.appendChild(document.createElement('iframe')); - this.tree = Tree.create(this.itemTree.getElementsByTagName('ul')[0], { - ajaxExpansion: this.ajaxExpansion, - getIdx: function() { - return this.getElementsByTagName('a')[0].getAttribute('rel'); - }, - idxBase : 'selector-' + this.getName() + '-', - dropdownField : this, - onselect : this.tree_click - }); - - // Select the appropriate items - var selectedItems = this.inputTag.value.split(/ *, */); - var i, isSelected = {}; - for(i=0;i