Author: boutell
Date: 2010-01-11 22:27:27 +0100 (Mon, 11 Jan 2010)
New Revision: 26491

Added:
   plugins/pkToolkitPlugin/trunk/web/js/pkControls-with-nesting-but-slow.js
Log:
experimental version with support for nesting. Unfortunately while this 
implementation is (mostly)
correct there's a nasty performance problem with jQuery when we use those :has 
selectors we like so much



Added: plugins/pkToolkitPlugin/trunk/web/js/pkControls-with-nesting-but-slow.js
===================================================================
--- plugins/pkToolkitPlugin/trunk/web/js/pkControls-with-nesting-but-slow.js    
                        (rev 0)
+++ plugins/pkToolkitPlugin/trunk/web/js/pkControls-with-nesting-but-slow.js    
2010-01-11 21:27:27 UTC (rev 26491)
@@ -0,0 +1,731 @@
+// Copyright 2009 P'unk Avenue LLC. Released under the MIT license.
+// See http://www.symfony-project.org/plugins/pkToolkitPlugin and
+// http://www.punkave.com/ for more information.
+
+// pkMultipleSelect transforms multiple selection elements into
+// attractive, user-friendly replacements. 
+
+// pkRadioSelect transforms single-select elements into a set of pill 
+// buttons (you probably won't always want this, it's handy when used 
+// selectively). 
+
+// pkSelectToList transforms single-select elements into
+// <ul> lists in which the list items set the underlying, hidden
+// select element and then immediately submit the form. There is also
+// special support for lists of tags with a count in parentheses
+// following the tag name in the label, like so: 
+//
+// <option value="foo">foo (5)</option>
+
+// Now accepts options. Valuable since you probably want to set the
+// choose-one option to something like "Select to Add" in a
+// progressive-enhancement scenario where you haven't manually provided
+// a first option that is actually a label
+
+function pkMultipleSelectAll(options)
+{
+       if (!options)
+       {
+               options = {};
+       }
+  $(document).ready(
+    function() {
+      pkMultipleSelect('body', options);
+    }
+  );
+}
+
+// Transforms multiple select elements into a much more attractive
+// and user-friendly control with a pulldown on the left and links
+// to remove already-selected choices on the right
+
+function pkMultipleSelect(target, options)
+{
+  $(target + ' select[multiple]').each(
+    function(i) {
+      var name = $(this).attr('name');
+      var id = $(this).attr('id');
+      var values = [];
+      var labels = [];
+      var selected = [];
+      var j;
+
+                       // By default the first option is assumed to be a 
"choose one" label and cannot actually
+                       // be chosen. If you are upgrading multiple select 
elements that weren't designed expressly
+                       // for this purpose, this is not great. However, if you 
specify an explicit 'choose-one'
+                       // text, a custom first option will be inserted with 
that text. Recommended for 
+                       // true progressive enhancement scenarios.
+                       if (options['choose-one'])
+                       {
+                               values.push('');
+                               labels.push(options['choose-one']);
+                               selected.push(false);
+                       }
+                       
+      for (j = 0; (j < this.options.length); j++)
+      {
+        var option = this.options[j];
+        values.push(option.value);
+        labels.push(option.innerHTML);
+        // Firefox is a little cranky about this,
+        // try it both ways
+        selected.push(option.getAttribute('selected') || option.selected);
+      }
+      if (id === '')
+      {
+        // Hopefully unique
+        id = name;
+      }
+      var html = "<div class='pk-multiple-select' id='" + id + "'>";
+      html += "<select name='select-" + name + "'>";
+      html += "</select>\n";
+      for (j = 0; (j < values.length); j++)
+      {
+        html += "<input type='checkbox' name='" + name + "'";
+        if (selected[j])
+        {
+          html += " checked";
+        }
+        html += " value=\"" + pkHtmlEscape(values[j]) + 
+          "\" style='display: none'/>";
+      }
+      html += "<ul class='pk-multiple-select-list'>";
+      if (!options['remove'])
+      {
+        options['remove'] = ' <span>Remove</span>';
+      }
+      for (j = 0; (j < values.length); j++)
+      {
+        html += "<li style='display: none'><a href='#' title='Remove this 
item' class='pk-multiple-select-remove'>" + 
+          labels[j] + 
+          options['remove'] + "</a></li>\n";
+      }
+      html += "</ul>\n";
+      // Handy for clearing floats
+      html += "<div class='pk-multiple-select-after'></div>\n";
+      html += "</div>\n";
+      $(this).replaceWith(html);
+      var select = $("#" + id + " select");
+      var k;
+      var items = $('#' + id + ' ul li');
+      for (k = 0; (k < values.length); k++)
+      {
+        $(items[k]).data("boxid", values[k]);
+        $(items[k]).click(function() { update($(this).data("boxid")); return 
false; });
+      }
+      function update(remove)
+      {
+        var ul = $("#" + id + " ul");
+        var select = $("#" + id + " select")[0];
+        var index = select.selectedIndex;
+        var value = false;
+        if (index > 0)
+        {
+          value = select.options[index].value;
+        }
+        var boxes = $('#' + id + " input[type=checkbox]");
+        for (k = 1; (k < values.length); k++)
+        {
+          if (boxes[k].value === remove)
+          {
+            boxes[k].checked = false;
+          }
+          if (boxes[k].value === value)
+          {
+            boxes[k].checked = true;
+          }
+        }
+        var items = $('#' + id + ' ul li');
+        var k;
+        var html;
+        for (k = 0; (k < values.length); k++)
+        {
+          if (boxes[k].checked)
+          {
+            $(items[k]).show();
+          }
+          else
+          {
+            $(items[k]).hide();
+            html += "<option ";
+            if (k == 0)
+            {
+              // First option is "pick one" message
+              html += " selected ";
+            }
+            html += "value=\"" + pkHtmlEscape(values[k]) + "\">" +
+              labels[k] + "</option>";
+          }
+        }
+        // Necessary in IE
+        $(select).replaceWith("<select name='select-" + name + "'>" + html + 
"</select>");
+        $("#" + id + " select").change(function() { update(false); });
+      }
+      function pkHtmlEscape(html)
+      {
+        html = html.replace('&', '&amp;'); 
+        html = html.replace('<', '&lt;'); 
+        html = html.replace('>', '&gt;'); 
+        html = html.replace('"', '&quot;'); 
+        return html;
+      }  
+      update(false);
+    }
+  );
+}
+
+// Transforms select elements matching the specified selector.  
+// You won't want to do this to every select element in your form,
+// so give them a class like .pk-radio-select and use a class selector like 
+// .pkRadioSelect (but not .pk-radio-select-container, which we use for
+// the span that encloses our toggle buttons). Make sure your selector is 
+// specific enough not to match other elements as well.
+//
+// We set the pk-radio-option-selected class on the currently selected link
+// element.
+//
+// If the autoSubmit option is true, changing the selection immediately
+// submits the form. There are no guarantees that will work with
+// wacky AJAX forms. It works fine with normal forms.
+//
+// Note the getOption calls that allow the use of custom templates.
+
+function pkRadioSelect(target, options)
+{
+  $(target).each(
+    function(i) {
+                       // Don't do it twice to the same element
+                       if ($(this).data('pk-radio-select-applied'))
+                       {
+                               return;
+                       }
+      $(this).hide();
+                       $(this).data('pk-radio-select-applied', 1);
+      var html = "";
+      var links = "";
+      var j;
+                       var total = this.options.length;
+      linkTemplate = getOption("linkTemplate",
+        "<a href='#'>_LABEL_</a>");
+      spanTemplate = getOption("spanTemplate",
+        "<span class='pk-radio-select-container'>_LINKS_</span>");
+      betweenLinks = getOption("betweenLinks", " ");
+      autoSubmit = getOption("autoSubmit", false);
+      for (j = 0; (j < this.options.length); j++)
+      {
+        if (j > 0)
+        {
+          links += betweenLinks;
+        }
+        links += 
+          linkTemplate.replace("_LABEL_", $(this.options[j]).html());
+      }
+      span = $(spanTemplate.replace("_LINKS_", links));
+      var select = this;
+      links = span.find('a');
+      $(links[select.selectedIndex]).addClass('pk-radio-option-selected');
+      links.each(
+        function (j)
+        {
+          $(this).data("pkIndex", j);
+                                       $(this).addClass('option-'+j);
+                                       
+                                       if (j == 0)
+                                       {
+                                               $(this).addClass('first');
+                                       }
+                                       
+                                       if (j == total-1)
+                                       {
+                                               $(this).addClass('last');       
                                        
+                                       }
+          $(this).click(
+            function (e)
+            {
+              select.selectedIndex = $(this).data("pkIndex");
+              var parent = ($(this).parent());
+              parent.find('a').removeClass('pk-radio-option-selected'); 
+              $(this).addClass('pk-radio-option-selected'); 
+              if (autoSubmit)
+              {
+                select.form.submit();
+              }
+              return false;
+            }
+          );
+        }
+      );
+      $(this).after(span);
+      function getOption(name, def)
+      {
+        if (name in options)
+        {
+          return options[name];
+        }
+        else
+        {
+          return def;
+        }
+      }
+    }
+  );
+}
+
+// Simple usage example
+// pkSelectToList('#pk-media-type', {} );
+
+// Usage example where the labels are tags with usage counts, with the
+// both alphabetical and popular tag lists present and custom labels:
+
+// pkSelectToList('#pk-media-tag', 
+//   { 
+//     tags: true,
+//     // MUST contain an anchor tag so our code can bind the click 
+//     currentTemplate: "<h5>_LABEL_ <a href='#'><font 
color='red'><i>x</i></font></a></h5>",
+//     popularLabel: "<h4>Popular Tags</h4>",
+//     popular: <?php echo pkMediaTools::getOption('popular_tags') ?>,
+//     alpha: true,
+//     // If this contains an 'a' tag it gets turned into a toggle 
+//     allLabel: "<h4><a href='#'>All Tags</a></h4>",
+//     itemTemplate: "_LABEL_ <span>(_COUNT_)",
+//     allVisible: false,
+//     all: true
+//   });
+
+function pkSelectToList(selector, options)
+{
+  $(selector).each(
+    function(i) {
+      $(this).hide();
+      var total = this.options.length;
+      var html = "<ul>";
+      var selectElement = this;
+      var tags = options['tags'];
+      var popular = false;
+      var alpha = false;
+      var all = true;
+      var itemTemplate = options['itemTemplate'];
+      if (!itemTemplate)
+      {
+        if (tags)
+        {
+          itemTemplate = "_LABEL_ <span>(_COUNT_)";
+        }
+        else
+        {
+          itemTemplate = "_LABEL_";
+        }
+      }
+      var currentTemplate;
+      if (tags)
+      {
+        popular = options['popular'];
+        all = options['all'];
+        alpha = options['alpha'];
+      }
+      if (options['currentTemplate'])
+      {
+        currentTemplate = options['currentTemplate'];
+      }
+      else
+      {
+        currentTemplate = "<h5>_LABEL_ <a href='#'><font 
color='red'><i>x</i></font></a></h5>";
+      }
+      var data = [];
+      var re = /^(.*)?\s+\((\d+)\)\s*$/;
+      index = -1;
+      for (i = 0; (i < total); i++)
+      {
+        var html = this.options[i].innerHTML;
+        if (tags)
+        {
+          var result = re.exec(html);
+          if (result)
+          {
+            data.push({ 
+              label: result[1], 
+              count: result[2], 
+              value: this.options[i].value
+            });
+          }
+          else
+          {
+            continue;
+          }
+        } 
+        else
+        {
+          // Test... carefully... for a non-empty string
+          if ((this.options[i].value + '') !== '')
+          {
+            data.push({
+              label: html,
+              value: this.options[i].value
+            });
+          }
+          else
+          {
+            continue;
+          }
+        }
+        if (selectElement.selectedIndex == i)
+        {
+          // Don't let skipped valueless entries throw off the index
+          index = data.length - 1;
+        }
+      }
+      // Make our after() calls in the reverse order so we get
+      // the correct final order
+      if (all)
+      {
+        var sorted = data.slice();
+        if (alpha)
+        {
+          sorted = sorted.sort(sortItemsAlpha);
+        }
+                               var lclass = options['listAllClass'];
+        var allList = appendList(sorted, lclass);
+        if (!options['allVisible'])
+        {
+          allList.hide();
+        }
+        if (options['allLabel'])
+        {
+          var allLabel = $(options['allLabel']);
+          if (allLabel)
+          {
+            var a = allLabel.find('a');
+            if (a)
+            {
+              a.click(function() 
+              {
+                allList.toggle("slow");
+                return false;
+              });
+            }
+          }
+          $(selectElement).after(allLabel);
+        }
+      }
+      if (popular)
+      {
+        var sorted = data.slice();
+        sorted = sorted.sort(sortItemsPopular);
+        sorted = sorted.slice(0, popular);
+        appendList(sorted, options['listPopularClass']);
+        if (options['popularLabel'])
+        {
+          $(selectElement).after($(options['popularLabel']));
+        }
+      }
+      if (index >= 0)
+      {
+        var current = currentTemplate;
+        current = current.replace("_LABEL_", data[index].label);
+        current = current.replace("_COUNT_", data[index].count);
+        current = $(current);
+        var a = current.find('a');
+        a.click(function()
+        {
+          selectElement.selectedIndex = 0;
+          $(selectElement.form).submit();
+          return false;
+        });
+        $(selectElement).after(current);
+      }
+      function appendList(data, c)
+      {
+        var list = $('<ul></ul>');
+        if (c)
+        {
+          list.addClass(c);
+        }
+        for (i = 0; (i < data.length); i++)
+        {
+          var item = itemTemplate;
+          if (tags)
+          {
+            item = item.replace("_COUNT_", data[i].count);
+          }
+          item = item.replace("_LABEL_", data[i].label);
+          var liHtml = "<li><a href='#'>" + item + "</a></li>";
+          var li = $(liHtml);
+          var a = li.find('a');
+          a.data('label', data[i].label);
+          a.data('value', data[i].value);
+          a.click(function() {
+            $(selectElement).val($(this).data('value'));
+            $(selectElement.form).submit();
+            return false;
+          });
+          list.append(li);
+        }
+        $(selectElement).after(list); 
+        return list;
+      }
+    }
+  );
+  function sortItemsAlpha(a, b)
+  {
+    x = a.label.toLowerCase();
+    y = b.label.toLowerCase();
+    // JavaScript has no <> operator 
+    return x > y ? 1 :x < y ? -1 : 0;
+  }
+  function sortItemsPopular(a, b)
+  {
+    // Most popular should appear first
+    return b.count - a.count;
+  }
+}
+
+// Labeling input elements compactly using their value attribute (i.e. search 
fields)
+
+// You have an input element. You want it to say 'Search' or a similar label, 
+// provided its initial value is empty. On focus, if the label is present, it 
should clear.
+// If they defocus it and it's empty, you want the label to come back. Here 
you go.
+
+function pkInputSelfLabel(selector, label)
+{
+       var pkInput = $(selector);
+       
+       pkInput.each(function() {
+               setLabelIfNeeded(this);
+       });
+
+       pkInput.focus(function() {
+               clearLabelIfNeeded(this);
+       });
+
+       pkInput.blur(function() {
+               setLabelIfNeeded(this);
+       });
+       
+       function setLabelIfNeeded(e)
+       {
+               var v = $(e).val();
+               if (v === '')
+               {
+                       $(e).val(label).addClass('pk-default-value');           
                
+               }
+       }
+       function clearLabelIfNeeded(e)
+       {
+               var v = $(e).val();
+               if (v === label)
+               {
+                       $(e).val('').removeClass('pk-default-value');
+               }
+       }
+}
+
+// Got a checkbox and a set of related controls that should only be enabled
+// when the checkbox is checked? Here's your answer
+
+// The optional hideItemsSelector contains items that should be hidden rather 
than disabled.
+// You can pass undefined for itemsSelector if you are only interested in 
hiding items. This
+// combination is useful if you want to fully disable a datepicker with an 
input field
+// (disable-able) and a calendar icon (not disable-able, but hide-able).
+
+// Both selector arguments are optional. To skip itemsSelector, pass undefined 
(not null)
+// for that argument.
+
+// ACHTUNG: don't forget about hidden form elements you might be disabling 
(Symfony adds them to the last row
+// in a form). Write your selectors carefully, check for over-generous 
selectors when forms seem broken.
+
+// Nesting is permitted. If an outer checkbox would enable a child of an inner 
checkbox, it first checks
+// a nesting counter to ensure it is not also disabled due to the inner 
checkbox.
+
+// pkCheckboxDisables does the opposite, disabling the indicated items when the
+// checkbox is checked.
+
+function pkCheckboxEnables(boxSelector, itemsSelector, hideItemsSelector)
+{
+       pkCheckboxEnablesBody(boxSelector, itemsSelector, hideItemsSelector, 
true);
+}
+
+function pkCheckboxDisables(boxSelector, itemsSelector, hideItemsSelector)
+{
+       pkCheckboxEnablesBody(boxSelector, itemsSelector, hideItemsSelector, 
false);
+}
+
+function pkCheckboxEnablesBody(boxSelector, itemsSelector, hideItemsSelector, 
enable)
+{
+       $(boxSelector).data('pkCheckboxEnablesItemsSelector', itemsSelector);
+       $(boxSelector).data('pkCheckboxEnablesHideItemsSelector', 
hideItemsSelector);
+       $(boxSelector).data('pkCheckboxEnablesEnable', enable);
+       $(boxSelector).click(function() 
+       {
+               update(this);
+       });
+
+       function bumpEnabled(selector)
+       {
+               var enabled = [];
+               $(selector).each(function() { 
+                       var counter = 
$(this).data('pkCheckboxEnablesEnableCounter');
+                       if (counter < 0)
+                       {
+                               counter++;
+                               $(this).data('pkCheckboxEnablesEnableCounter', 
counter);
+                       }
+                       if (counter >= 0)
+                       {
+                               enabled.push(this);
+                       }
+               });
+               return enabled;
+       }
+
+       function bumpDisabled(selector)
+       {
+               $(selector).each(function() { 
+                       var counter = 
$(this).data('pkCheckboxEnablesEnableCounter');
+                       if (counter === undefined)
+                       {
+                               counter = 0;
+                       }       
+                       counter--;
+                       $(this).data('pkCheckboxEnablesEnableCounter', counter);
+               });
+       }
+       
+       function update(checkbox)
+       {
+               var itemsSelector = 
$(checkbox).data('pkCheckboxEnablesItemsSelector');
+               var hideItemsSelector = 
$(checkbox).data('pkCheckboxEnablesHideItemsSelector');
+               var enable = $(checkbox).data('pkCheckboxEnablesEnable');
+               var checked = $(checkbox).attr('checked');
+               var state = checked;
+               if (!enable)
+               {
+                       state = !checked;
+               }
+               if (state)
+               {
+                       if (itemsSelector !== undefined)
+                       {
+                               var enabled = bumpEnabled(itemsSelector);
+                               for (var i in enabled)
+                               {
+                                       var item = enabled[i];
+                                       $(item).removeAttr('disabled');
+                               }
+                       }
+                       if (hideItemsSelector !== undefined)
+                       {
+                               var shown = bumpEnabled(hideItemsSelector);
+                               for (var i in shown)
+                               {
+                                       var item = shown[i];
+                                       $(item).show();
+                               }
+                       }
+               }
+               else
+               {
+                       if (itemsSelector !== undefined)
+                       {
+                               bumpDisabled(itemsSelector);
+                               $(itemsSelector).attr('disabled', 'disabled');
+                       }
+                       if (hideItemsSelector !== undefined)
+                       {
+                               bumpDisabled(hideItemsSelector);
+                               $(hideItemsSelector).hide();
+                       }
+               } 
+       }
+       // At DOMready so we can affect controls created by js widgets in the 
form
+       $(function() {
+               $(boxSelector).each(function() { update(this) });
+       });
+}
+
+// Similar to the above, but for select options. itemsSelectors is a hash
+// of option values pointing to item selectors. On change all of the items
+// selectors for the other options get disabled, then the items selector for
+// the selected option (if any) gets enabled. Great for enabling a text field
+// when "Other" is chosen from an "Institution Type" menu.
+
+// If desired a second hash of option values pointing to item selectors that
+// should be shown/hidden rather than enabled/disabled can also be passed.
+
+// Both selector arguments are optional. To skip itemsSelectors, pass 
undefined (not null)
+// for that argument.
+
+function pkSelectEnables(selectSelector, itemsSelectors, hideItemsSelectors)
+{
+       $(selectSelector).data('pkSelectEnablesItemsSelectors', itemsSelectors);
+       $(selectSelector).data('pkSelectEnablesHideItemsSelectors', 
hideItemsSelectors);
+       $(selectSelector).change(function() {
+               update(this);
+       });
+
+       function update(select)
+       {
+               var itemsSelectors = 
$(select).data('pkSelectEnablesItemsSelectors');
+               var hideItemsSelectors = 
$(select).data('pkSelectEnablesHideItemsSelectors');
+               if (itemsSelectors !== undefined)
+               {
+                       for (var option in itemsSelectors)
+                       {
+                               $(itemsSelectors[option]).attr('disabled', 
'disabled');
+                       }
+                       var option = select.value;
+                       if (itemsSelectors[option])
+                       {
+                               
$(itemsSelectors[option]).removeAttr('disabled');
+                       }
+               }
+               if (hideItemsSelectors !== undefined)
+               {
+                       for (var option in hideItemsSelectors)
+                       {
+                               $(hideItemsSelectors[option]).hide();
+                       }
+                       var option = select.value;
+                       if (hideItemsSelectors[option])
+                       {
+                               $(hideItemsSelectors[option]).show();
+                       }
+               }
+       }
+       $(function() {
+               $(selectSelector).each(function() { update(this) });
+       });
+}
+
+
+function pkBusy(selector)
+{
+       $(selector).each(function() {
+               $(this).data('pk-busy-html', $(this).html());
+               $(this).html("<img 
src=\"/pkToolkitPlugin/images/ajax-loader.gif\"/>");
+       });
+}
+
+function pkReady(selector)
+{
+       $(selector).each(function() {
+               $(this).html($(this).data('pk-busy-html'));
+       });
+}
+
+// Select elements with only one preselected <option> are better presented as 
static content.
+// This gives prettier results with a generic echo $form for things like RSVP 
forms that
+// don't always have more than one possible new state.
+
+// Usage: pkSelectToStatic('body') or something less promiscuous
+
+function pkSelectToStatic(selector)
+{
+       $(selector).find('select').each(function() {
+               if ((this.options.length == 1) && (this.options[0].selected))
+               {
+                       $(this).after('<span class="pk-static-select">' + 
this.options[0].innerHTML + '</span>');
+                       $(this).hide();
+               }
+       });
+}
+

-- 
You received this message because you are subscribed to the Google Groups 
"symfony SVN" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/symfony-svn?hl=en.


Reply via email to