Refactor the new, pretty, autocomplete code (preparing to move it to
authorMichael J. Rubinsky <mrubinsk@horde.org>
Thu, 23 Apr 2009 20:05:43 +0000 (16:05 -0400)
committerMichael J. Rubinsky <mrubinsk@horde.org>
Thu, 23 Apr 2009 20:05:43 +0000 (16:05 -0400)
Horde's scope):

- Don't force client code to constrcut the DOM structure need for this to work.
  All we need to do is add a single input element, and attach it to the autocompleter.
- Allow overriding of CSS Classes and DOM ids used to allow for mulitple scopes/multiple
  autocompleters on the same page.

kronolith/index.php
kronolith/js/src/kronolith.js
kronolith/js/src/taggerAutoCompleter.js
kronolith/lib/Imple/TagAutoCompleter.php
kronolith/templates/index/index.inc
kronolith/themes/screen.css

index 682c7fe..a2f0efa 100644 (file)
@@ -65,12 +65,5 @@ require KRONOLITH_TEMPLATES . '/index/index.inc';
 Kronolith::includeScriptFiles();
 Kronolith::outputInlineScript();
 $notification->notify(array('listeners' => array('javascript')));
-Kronolith_Imple::factory('TagAutoCompleter',
-     array('box' => 'kronolithTagACBox',
-           'triggerId' => 'kronolithTagACTrigger',
-           'tags' => 'kronolithEventTags',
-           'container' => 'kronolithTagACContainer'
-           //'existing' => array('tag1', 'tag2'),
-           //'debug' => true
-           ));
+Kronolith_Imple::factory('TagAutoCompleter', array('triggerId' => 'tags', 'pretty' => true));
 echo "</body>\n</html>";
index 99863eb..49d73c7 100644 (file)
@@ -1184,7 +1184,7 @@ KronolithCore = {
                 e.stop();
                 return;
             } else if (elt.hasClassName('kronolithEventTag')) {
-                $('kronolithTagACTrigger').kronolithTagger.addNewTagNode(elt.getText());
+                $('tags').tagger.addNewTagNode(elt.getText());
                 e.stop();
                 return;
             }
@@ -1238,7 +1238,7 @@ KronolithCore = {
             RedBox.onDisplay = null;
         };
 
-        $('kronolithTagACTrigger').kronolithTagger.init();
+        $('tags').tagger.init();
         $('kronolithEventForm').enable();
         $('kronolithEventForm').reset();
         this.doAction('ListTopTags', {}, this._topTags);
@@ -1294,7 +1294,7 @@ KronolithCore = {
         $('kronolithEventStartTime').value = ev.st;
         $('kronolithEventEndDate').value = ev.ed;
         $('kronolithEventEndTime').value = ev.et;
-        $('kronolithTagACTrigger').kronolithTagger.init(ev.tg);
+        $('tags').tagger.init(ev.tg);
         if (ev.r) {
             // @todo: refine
             $A($('kronolithEventRecurrence').options).find(function(option) {
index 4b08026..b9cc122 100644 (file)
@@ -3,15 +3,46 @@ var KronolithTagger = Class.create({
         {
             this.p = params;
 
-            trigger = $(this.p.trigger);
+            // Array to hold the currently selected tags to ease with removing
+            // them, assuring no duplicates etc..
+            this.selectedTags = [];
+
+            // The outter most box, the "fake" input element.
+            if (typeof this.p.box == 'undefined') {
+                this.p.box = this.p.box = 'HordeACBox';
+            }
+            if (typeof this.p.boxClass == 'undefined') {
+                this.p.boxClass = 'hordeACBox';
+            }
+
+            // The class for the ul. li will have a *Item and *Member class
+            // added to them.
+            if (typeof this.p.listClass == 'undefined') {
+                this.p.listClass = 'hordeACList';
+            }
+
+            // Class for the actual input element.
+            if (typeof this.p.growingInputClass == 'undefined') {
+                this.p.growingInputClass = 'hordeACTrigger';
+            }
+            if (typeof this.p.triggerContainer == 'undefined') {
+                this.p.triggerContainer = 'hordeACTriggerContainer';
+            }
+
+            // p.tags is now the hidden input field, while p.trigger will
+            // contain the "real" input element's name attribute.
+            this.p.tags = this.p.trigger;
+            this.p.trigger = this.p.trigger + 'real';
+
+            this.buildStructure();
+
+            var trigger = $(this.p.trigger);
             trigger.observe('keydown', this._onKeyDown.bindAsEventListener(this));
 
             // Bind this object to the trigger so we can call it's methods
             // from client code.
-            trigger.kronolithTagger = this;
-
-            // Make sure the right dom elements are styled correctly.
-            $(this.p.container).addClassName('kronolithACListItem kronolithTagACContainer');
+            //trigger.kronolithTagger = this;
+            $(this.p.tags).tagger = this;
 
             // Make sure the p.tags element is hidden
             if (!this.p.debug) {
@@ -25,7 +56,8 @@ var KronolithTagger = Class.create({
             $(this.p.box).observe('click', function() {$(this.p.trigger).focus()}.bindAsEventListener(this));
 
             // Create the underlaying Autocompleter
-            new Ajax.Autocompleter(params.trigger, params.resultsId, params.uri, params.params);
+            this.p.uri = this.p.uri + '/input=' + this.p.trigger;
+            new Ajax.Autocompleter(this.p.trigger, this.p.trigger + '_results', this.p.uri, this.p.params);
 
             // Prepopulate the tags and the container elements?
             if (typeof this.p.existing != 'undefined') {
@@ -38,8 +70,8 @@ var KronolithTagger = Class.create({
         {
             // TODO: Resize the trigger field to fill the current line?
             // Clear any existing values
-            if (this.p.selectedTags.length) {
-                $('kronolithTagACBox').select('li.kronolithTagACListItem').each(function(item) {this.removeTagNode(item) }.bind(this));
+            if (this.selectedTags.length) {
+                $(this.p.box).select('li.' + this.p.listClass + 'Item').each(function(item) {this.removeTagNode(item) }.bind(this));
             }
 
             // Clear the hidden tags field
@@ -54,6 +86,39 @@ var KronolithTagger = Class.create({
 
         },
 
+        buildStructure: function()
+        {
+            // Build the outter box
+            var box = new Element('div', {id: this.p.box, 'class': this.p.boxClass});
+
+            // The results div - where the autocomplete choices are placed.
+            var results = new Element('div', {id: this.p.trigger + '_results', 'class': 'autocomplete'});
+
+            // The list - where the choosen items are placed as <li> nodes
+            var list = new Element('ul', {'class':   this.p.listClass});
+
+            // The input element and the <li> wraper
+            var inputListItem = new Element('li', {'class': this.p.listClass + 'Member',
+                                                   'id': this.p.triggerContainer});
+
+            var growingInput = new Element('input', {'class': this.p.growingInputClass,
+                                                     'id': this.p.trigger,
+                                                     'name': this.p.trigger,
+                                                     'autocomplete':'off'});
+
+            inputListItem.update(growingInput);
+            list.update(inputListItem);
+            box.update(list);
+            box.insert(results);
+
+            // Replace the single input element with the new structure and
+            // move the old element into the structure while making sure it's
+            // hidden. (Use the long form to play nice with Opera)
+            oldTrigger = Element.replace($(this.p.tags), box);
+            box.insert(oldTrigger);
+
+        },
+
         _onKeyDown: function(e)
         {
             // Check for a comma
@@ -81,17 +146,17 @@ var KronolithTagger = Class.create({
         addNewTagNode: function(value)
         {
             // Don't add if it's already present.
-            for (var x = 0, len = this.p.selectedTags.length; x < len; x++) {
-                if (this.p.selectedTags[x] == value) {
+            for (var x = 0, len = this.selectedTags.length; x < len; x++) {
+                if (this.selectedTags[x] == value) {
                     return;
                 }
             }
 
-            var newTag = new Element('li', {class: 'kronolithACListItem kronolithTagACListItem'}).update(value);
-            var x = new Element('img', {class: 'kronolithTagACRemove', src:this.p.URI_IMG_HORDE + "/delete-small.png"});
+            var newTag = new Element('li', {class: this.p.listClass + 'Member ' + this.p.listClass + 'Item'}).update(value);
+            var x = new Element('img', {class: 'hordeACItemRemove', src:this.p.URI_IMG_HORDE + "/delete-small.png"});
             x.observe('click', this._removeTagHandler.bindAsEventListener(this));
             newTag.insert(x);
-            $(this.p.container).insert({before: newTag});
+            $(this.p.triggerContainer).insert({before: newTag});
             $(this.p.trigger).value = '';
 
             // Add to hidden input field.
@@ -102,15 +167,15 @@ var KronolithTagger = Class.create({
             }
 
             // ...and keep the selectedTags array up to date.
-            this.p.selectedTags.push(value);
+            this.selectedTags.push(value);
         },
 
         removeTagNode: function(item)
         {
             var value = item.collectTextNodesIgnoreClass('informal');
-            for (var x = 0, len = this.p.selectedTags.length; x < len; x++) {
-                if (this.p.selectedTags[x] == value) {
-                    this.p.selectedTags.splice(x, 1);
+            for (var x = 0, len = this.selectedTags.length; x < len; x++) {
+                if (this.selectedTags[x] == value) {
+                    this.selectedTags.splice(x, 1);
                 }
             }
             item.remove();
@@ -120,6 +185,6 @@ var KronolithTagger = Class.create({
         {
             item = Event.element(e).up();
             this.removeTagNode(item);
-            $(this.p.tags).value = this.p.selectedTags.join(',');
+            $(this.p.tags).value = this.selectedTags.join(',');
         }
 });
\ No newline at end of file
index 0014c6c..638d853 100644 (file)
@@ -33,7 +33,7 @@ class Kronolith_Imple_TagAutoCompleter extends Kronolith_Imple
 
     /**
      * Attach the Imple object to a javascript event.
-     * Assume that if the 'box' parameter is empty then we want a
+     * If the 'pretty' parameter is empty then we want a
      * traditional autocompleter, otherwise we get a spiffy pretty one.
      *
      */
@@ -43,10 +43,12 @@ class Kronolith_Imple_TagAutoCompleter extends Kronolith_Imple
         parent::attach();
         Horde::addScriptFile('autocomplete.js', 'horde', true);
 
-        if ($pretty = !empty($this->_params['box'])) {
+        if ($pretty = !empty($this->_params['pretty'])) {
             Horde::addScriptFile('taggerAutoCompleter.js', 'kronolith', true);
+            $this->_params['uri'] =  Horde::url($GLOBALS['registry']->get('webroot', 'kronolith') . '/imple.php?imple=TagAutoCompleter', true);
+        } else {
+            $this->_params['uri'] =  Horde::url($GLOBALS['registry']->get('webroot', 'kronolith') . '/imple.php?imple=TagAutoCompleter/input=' . rawurlencode($this->_params['triggerId']), true);
         }
-        $this->_params['uri'] =  Horde::url($GLOBALS['registry']->get('webroot', 'kronolith') . '/imple.php?imple=TagAutoCompleter/input=' . rawurlencode($this->_params['triggerId']), true);
 
         if (!$pretty) {
             $params = array(
@@ -69,13 +71,9 @@ class Kronolith_Imple_TagAutoCompleter extends Kronolith_Imple
         $params[] = '{' . implode(',', $js_params) . '}';
 
         if ($pretty) {
-            $js_vars = array('box' => $this->_params['box'],
-                             'resultsId' => $this->_params['resultsId'],
+            $js_vars = array('boxClass' => 'hordeACBox kronolithLongField',
                              'uri' => $this->_params['uri'],
-                             'selectedTags' => array(),
                              'trigger' => $this->_params['triggerId'],
-                             'tags' => $this->_params['tags'],
-                             'container' => $this->_params['container'],
                              'URI_IMG_HORDE' => $registry->getImageDir('horde'),
                              'params' => $params);
 
@@ -83,7 +81,7 @@ class Kronolith_Imple_TagAutoCompleter extends Kronolith_Imple
                 $js_vars['existing'] = $this->_params['existing'];
             }
 
-           $script = array('new KronolithTagger(' . Horde_Serialize::serialize($js_vars, Horde_Serialize::JSON, NLS::getCharset()) . ')');
+            $script = array('new KronolithTagger(' . Horde_Serialize::serialize($js_vars, Horde_Serialize::JSON, NLS::getCharset()) . ')');
         } else {
             $script = array('new Ajax.Autocompleter(' . implode(',', $params) . ')');
         }
index 1876281..53c89ad 100644 (file)
 
 <div>
   <label for="id_tags"><?php echo _("Tags") ?></label><br />
-  <div id="kronolithTagACBox" class="kronolithLongField">
-   <ul class="kronolithTagACList">
-    <li id="kronolithTagACContainer" class="kronolithACListItem kronolithTagACContainer"><input name="kronolithTagACTrigger" id="kronolithTagACTrigger" type="text" /></li>
-   </ul>
-   <div id="kronolithTagACTrigger_results" class="autocomplete"></div>
-  </div>
-  <input type="hidden" id="kronolithEventTags" name="tags" />
-  <span id="kronolithTagACTrigger_loading_img" style="display:none;"><?php echo Horde::img('loading.gif', _("Loading...")) ?></span>
+  <input id="tags" name="tags" />
+  <span id="tags_loading_img" style="display:none;"><?php echo Horde::img('loading.gif', _("Loading...")) ?></span>
 </div>
 
 <div id="kronolithEventTopTags"></div>
index d55aa9b..ba74776 100644 (file)
@@ -552,28 +552,28 @@ body.kronolithAjax {
 
 /* to be removed */
 div.c1 {
-       color: #336600;
+    color: #336600;
 }
 div.c2 {
-       color: #1a5587;
+    color: #1a5587;
 }
 div.c3 {
-       color: #d38a23;
+    color: #d38a23;
 }
 div.c4 {
-       color: #4b2a16;
+    color: #4b2a16;
 }
 div.c1.kronolithEventFull {
-       background-color: #336600 !important;
+    background-color: #336600 !important;
 }
 div.c2.kronolithEventFull {
-       background-color: #1a5587 !important;
+    background-color: #1a5587 !important;
 }
 div.c3.kronolithEventFull {
-       background-color: #d38a23 !important;
+    background-color: #d38a23 !important;
 }
 div.c4.kronolithEventFull {
-       background-color: #4b2a16 !important;
+    background-color: #4b2a16 !important;
 }
 
 #kronolithAddEvents span {
@@ -1280,61 +1280,6 @@ li.panel-tags {
     text-decoration: underline;
 }
 
-#kronolithTagACBox {
-    background:white none repeat scroll 0 0;
-    border:1px solid #666699;
-    cursor:default;
-    /*overflow:hidden;*/
-    text-align:left;
-    min-height: 58px;
-}
-
-.kronolithTagACList {
-    display:block;
-    overflow:hidden;
-    width:100%;
-    cursor:default;
-    list-style-type:none;
-    margin:0;
-    padding:0;
-}
-
-.kronolithACListItem {
-    display:inline-block;
-    font-size:90%;
-    padding:2px 1px 1px 2px;
-    height: 17px;
-    white-space:nowrap;
-}
-
-.kronolithTagACListItem {
-    background-color:lightblue;
-    border:1px solid #C0C0C0;
-    -moz-border-radius-bottomleft:4px;
-    -moz-border-radius-bottomright:4px;
-    -moz-border-radius-topleft:4px;
-    -moz-border-radius-topright:4px;
-    margin:5px;
-}
-
-#kronolithTagACTrigger {
-    background:transparent none repeat scroll 0 0;
-    border:0 none;
-    display:inline;
-    position:static;
-    width:80px;
-}
-
-.kronolithTagACContainer {
-    height:18px;
-    margin:2px;
-    padding:2px 2px 2px 0;
-}
-
-.kronolithTagACRemove:hover {
-    cursor:pointer;
-}
-
 #hordeAlerts div.kronolith-sticky, #hordeAlerts div.kronolith-sticky {
     background-color: #ebe20c;
     background-image: url("graphics/warning.png");