PageRenderTime 24ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/public_html/minejs/jquery.miner.taglist.js

http://pymine.googlecode.com/
JavaScript | 283 lines | 224 code | 23 blank | 36 comment | 28 complexity | 97e0414c15b9255b6c1778a9ecf9d3ee MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1
  1. /*
  2. * Taglist autocomplete wrapper for the Mine API, Richard Marr
  3. *
  4. * Based on jQuery Autocomplete plugin 1.1 by Jรถrn Zaefferer (c) 2009 Jรถrn Zaefferer
  5. * http://docs.jquery.com/Plugins/Autocomplete
  6. *
  7. * Dual licensed under the MIT and GPL licenses:
  8. * http://www.opensource.org/licenses/mit-license.php
  9. * http://www.gnu.org/licenses/gpl.html
  10. *
  11. */
  12. /**
  13. * TODO:
  14. * - bug: handle errors on ajax
  15. * - like "your are not logged in"
  16. * - feature: let the mine api do the filtering instead of the js
  17. * - feature: hide already selected tags in tagpicker
  18. * - basic version DONE; tagsyntax aware version TODO
  19. * - feature: should also load implied tags for the existing tags
  20. * - feature: show separate cloud for "recently used tags" which can be selected
  21. * - feature: show separate cloud for "recommended tags" which can be selected;
  22. * these are tags which are frequently used in combination with the already added tags
  23. * - bug: in Opera when "shoes" is en existing tag typing "sh" RETURN adds two tags "sh" and "shoes"
  24. */
  25. ;(function($) {
  26. // wrap the generic autocomplete functionality (which can be reused) with tag-list specific code
  27. // replace a input element with a nice tagpicker, the input element becomes a hidden one, which can just be posted
  28. var tagLister = new(function() {
  29. var that = this;
  30. var input;
  31. var hiddenInput;
  32. var ul;
  33. var tags;
  34. var tagRetreiver;
  35. var KEY = {
  36. UP: 38,
  37. DOWN: 40,
  38. DEL: 46,
  39. TAB: 9,
  40. RETURN: 13,
  41. ESC: 27,
  42. COMMA: 188,
  43. PAGEUP: 33,
  44. PAGEDOWN: 34,
  45. BACKSPACE: 8
  46. };
  47. var TAGSYNTAX = {
  48. NONE : "",
  49. FOR : "for",
  50. IMPLIES : "implies",
  51. INVALID : "invalid",
  52. NEWTAG : "$$newtag$$"
  53. };
  54. $.extend(this, {
  55. init : function() {
  56. // "this" in this context is the element on which the tagLister is working
  57. var el = this;
  58. tags = that.extractExistingTags(el);
  59. that.replaceHTML(el);
  60. tagRetreiver = new that.TagRetreiver(that);
  61. that.addAutoCompleteBehaviour(input);
  62. that.addKeyDownBehaviour(input);
  63. that.addKeyRemoveTagBehaviour(ul);
  64. return hiddenInput;
  65. },
  66. replaceHTML : function(el) {
  67. var attrs = {
  68. id : el.attr("id"),
  69. name : el.attr("name"),
  70. value : tags.join(" "),
  71. "class" : el.attr("class"),
  72. size : el.attr('size')
  73. }
  74. var i=1;
  75. while ($("#taglist"+i).length) {i++}
  76. var widgetId = "taglist"+i;
  77. el.replaceWith('<div class="taglist-widget" id="'+widgetId+'"><input type="hidden" id="'+attrs.id+'" name="'+attrs.name+'" value="'+attrs.value+'" /></div>');
  78. hiddenInput = $("#"+widgetId+" input[type=hidden]");
  79. hiddenInput.after('<input type="text" class="'+attrs["class"]+'" size="'+attrs.size+'"/>');
  80. hiddenInput.before('<ul></ul>');
  81. input = $("#"+widgetId+" input[type=text]");
  82. ul = $("#"+widgetId+" ul");
  83. for (var i=0; i<tags.length; i++) {
  84. that.insertTagLi(tags[i]);
  85. }
  86. },
  87. extractExistingTags : function(el) {
  88. return that.splitTagString(el.attr("value"));
  89. },
  90. parseApiResult : function(data) {
  91. // opening the envelope
  92. // mapping data and value properties
  93. var result = [];
  94. for (var i=0; i<data.result.length; i++) {
  95. result[i] = data.result[i].tag;
  96. result[i].data = result[i].tagName;
  97. result[i].value = that.splitTagString(result[i].tagImplies)
  98. }
  99. return result;
  100. },
  101. addAutoCompleteBehaviour : function(input) {
  102. input.autocomplete(
  103. tagRetreiver.getTags,
  104. {
  105. minChars: 1,
  106. autoFill: false,
  107. mustMatch: false,
  108. cacheLength: 0,
  109. matchContains: true,
  110. scrollHeight: 220,
  111. formatItem: function(data, i, total, term) {
  112. return "<span class='tag list'>"+data.tagName+"</span> "+
  113. (data.tagImplies && data.tagImplies!=TAGSYNTAX.NEWTAG ? "<span class='tag cloud'> &lt; "+data.tagImplies+"</span>" : "");
  114. },
  115. parse : this.parseApiResult
  116. }
  117. ).result(function(event, tag, implied) {
  118. that.addTag(tag, implied);
  119. that.clearInput();
  120. });
  121. },
  122. clearInput : function(){
  123. input.attr("value", "");
  124. },
  125. insertTagLi : function(name, implied) {
  126. var className = that.hasTagSyntax(name);
  127. var implyHtml = "";
  128. name = name.replace('<', '&lt;');
  129. if (implied && implied.length) {
  130. className = TAGSYNTAX.IMPLIES;
  131. implyHtml = '<span> &lt; '+implied.join(' ')+'</span>';
  132. }
  133. ul.append('<li class="'+className+'" tag="'+name+'">'+name+implyHtml+' <span class="remove" title="remove">X</span></li>')
  134. },
  135. filter : function() {
  136. return input.attr("value");
  137. },
  138. splitTagString : function(string) {
  139. return $.grep((string||"").split(/\s+/), function(s){return s!=""});
  140. },
  141. addKeyDownBehaviour : function(el) {
  142. el.bind("keydown", function(event) {
  143. switch(event.keyCode) {
  144. case KEY.RETURN:
  145. event.preventDefault();
  146. var filter = that.filter();
  147. if(filter && that.hasTagSyntax(filter)!=TAGSYNTAX.INVALID) {
  148. that.addTag(filter);
  149. that.clearInput();
  150. }
  151. return false;
  152. break;
  153. }
  154. }).bind("keyup", function(event) {
  155. that.colorIfSpecialSyntax();
  156. that.markCurrentTag();
  157. })
  158. },
  159. addKeyRemoveTagBehaviour : function(ul) {
  160. ul.click(function(e) {
  161. var el = $(e.target);
  162. if(el.hasClass("remove")) {
  163. that.removeTag(el.closest("li").attr("tag"));
  164. }
  165. })
  166. },
  167. colorIfSpecialSyntax : function() {
  168. input.removeClass("for implies invalid");
  169. input.addClass(that.hasTagSyntax(that.filter()));
  170. },
  171. markCurrentTag : function() {
  172. if (that.currentTagMarked) {
  173. ul.find("li.current").removeClass("current");
  174. that.currentTagMarked = false;
  175. }
  176. var filter = that.filter().replace(/\s+/, '');
  177. if ($.inArray(filter, tags)!=-1) {
  178. ul.find("li[tag="+filter+"]").addClass("current");
  179. that.currentTagMarked = true;
  180. }
  181. },
  182. hasTagSyntax : function (tag) {
  183. if (tag.match(/^[a-zA-Z-0-9_]* *$/)) {
  184. return TAGSYNTAX.NONE
  185. } else if (tag.match(/^for *: *[a-zA-Z-0-9_]* *$/)) {
  186. return TAGSYNTAX.FOR
  187. } else if (tag.match(/^[a-zA-Z-0-9_]+ *< *[a-zA-Z-0-9_]* *$/)) {
  188. return TAGSYNTAX.IMPLIES
  189. } else {
  190. return TAGSYNTAX.INVALID
  191. }
  192. },
  193. addTag : function(name, implied) {
  194. name = (name && name.replace(/\s+/, '')) || "";
  195. if (implied==TAGSYNTAX.NEWTAG) {
  196. // auch this is ugly :'(
  197. name = name.match(/"([^]+)"/)[1];
  198. implied = null;
  199. }
  200. if ($.inArray(name, tags)==-1) {
  201. tags.push(name);
  202. hiddenInput.attr("value", tags.join(" "));
  203. that.insertTagLi(name, implied);
  204. }
  205. },
  206. removeTag : function(name) {
  207. ul.find("li[tag="+name+"]").remove();
  208. tags = $.grep(tags, function(t){return t!=name});
  209. hiddenInput.attr("value", tags.join(" "));
  210. },
  211. TagRetreiver : function(tagLister) {
  212. var completeResult = null;
  213. // assuming we're on the same domain, which is true for now
  214. var baseUrl = document.location.href.match(/[^:]+:\/\/[^\/]+/)[0];
  215. $.getJSON(
  216. baseUrl+"/api/tag.json",
  217. function(data) {
  218. completeResult = tagLister.parseApiResult(data);
  219. }
  220. );
  221. this.getTags = function() {
  222. var result = []
  223. var filter = tagLister.filter();
  224. var tagFound = false;
  225. // filter all results, mineapi should do this in the future
  226. if (completeResult) {
  227. result = $.grep(completeResult, function(entry) {
  228. if (entry.tagName == filter) {
  229. tagFound = true;
  230. }
  231. // ignore if it doesn't match
  232. if (entry.tagName.indexOf(filter)!=0) {
  233. return false;
  234. }
  235. // ignore already selected tags
  236. // console.log(tags, entry.tagName)
  237. if ($.inArray(entry.tagName, tags)!=-1) {
  238. return false;
  239. }
  240. return true;
  241. });
  242. }
  243. // add 'create tag' entry at the bottom
  244. filter = filter.replace(/\s+/, '');
  245. if (!tagFound && $.inArray(filter, tags)==-1) {
  246. result.push({
  247. tagImplies : '$$newtag$$',
  248. value : '$$newtag$$',
  249. tagName : 'create tag "'+filter+'"',
  250. data : 'create tag "'+filter+'"'
  251. })
  252. }
  253. return result;
  254. }
  255. }
  256. }
  257. );
  258. })();
  259. $.fn.extend({taglist: tagLister.init});
  260. })(jQuery);