/src/autocomplete.lisp

http://github.com/mtravers/wuwei · Lisp · 175 lines · 111 code · 12 blank · 52 comment · 3 complexity · 58a118638ba690aff70101fbcd8b5ed0 MD5 · raw file

  1. (in-package :wu)
  2. ;;; +=========================================================================+
  3. ;;; | Copyright (c) 2009, 2010 Mike Travers and CollabRx, Inc |
  4. ;;; | |
  5. ;;; | Released under the MIT Open Source License |
  6. ;;; | http://www.opensource.org/licenses/mit-license.php |
  7. ;;; | |
  8. ;;; | Permission is hereby granted, free of charge, to any person obtaining |
  9. ;;; | a copy of this software and associated documentation files (the |
  10. ;;; | "Software"), to deal in the Software without restriction, including |
  11. ;;; | without limitation the rights to use, copy, modify, merge, publish, |
  12. ;;; | distribute, sublicense, and/or sell copies of the Software, and to |
  13. ;;; | permit persons to whom the Software is furnished to do so, subject to |
  14. ;;; | the following conditions: |
  15. ;;; | |
  16. ;;; | The above copyright notice and this permission notice shall be included |
  17. ;;; | in all copies or substantial portions of the Software. |
  18. ;;; | |
  19. ;;; | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
  20. ;;; | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
  21. ;;; | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
  22. ;;; | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
  23. ;;; | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
  24. ;;; | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
  25. ;;; | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
  26. ;;; +=========================================================================+
  27. ;;; Author: Mike Travers
  28. (export '(auto-complete-field
  29. auto-complete-field-sparql
  30. in-place-field in-place-setf-field))
  31. #|
  32. Support for autocomplete and in-place-editor widgets
  33. See http://madrobby.github.com/scriptaculous/ajax-autocompleter/
  34. Requires a DOM element named "body" to control where the autocomplete box gets inserted.
  35. Todo:
  36. - layout and bounds stuff.
  37. - idea: a greyed out type indicator by default (apparently not supported by scriptaculous -- but it ought to layer on top OK).
  38. - completion machinery for replacing box with frame on comp
  39. updateElement
  40. - highlighting match (esp for :match-type :word)
  41. - style stuff should be pulled out
  42. |#
  43. (defun auto-complete-field (&key (id (string (gensym "id")))
  44. name
  45. value
  46. options
  47. completions-url
  48. completions-generator
  49. embedded-html
  50. on-selected
  51. textarea
  52. (update (string+ id "_auto_complete"))
  53. input-options
  54. (scroll? 30)
  55. spinner?
  56. )
  57. #.(doc "Generate an HTML autocompletion field. Arguments below (all except completions-url are optional)"
  58. "ID - the HTML ID of the element"
  59. "NAME - the name of the field"
  60. "VALUE - the current value of the field"
  61. "TEXTAREA - T to use a multi-line textarea"
  62. "OPTIONS - additional options to pass to the scriptaculous Ajax.Autocompleter object."
  63. "INPUT-OPTIONS - options to pass to the input or textarea tag (eg '((\"tokens\" . (\",\" #\Newline))))"
  64. "COMPLETIONS-GENERATOR - a procedure that takes a prefix and returns a list of (id . name) pairs"
  65. "EMBEDDED-HTML - T if strings can contain HTML markup"
  66. "COMPLETIONS-URL - a URL that supplies the completions. Either this or COMPLETIONS-GENERATOR must be supplied, but not both"
  67. "ON-SELECTED - a function that is called with the value, value string, and id of the selected option"
  68. "UPDATE - the HTML ID of the autocompletion box"
  69. "SCROLL? - If an integer, add scroll bar if more completions than this (default to 30)"
  70. "SPINNER? - T to show a spinner while fetching completions")
  71. (flet ((default-option (optname value)
  72. (unless (member optname options :key #'car :test #'equal)
  73. (push (cons optname value) options))))
  74. (default-option "paramName" "prefix")
  75. (when on-selected
  76. (default-option "afterUpdateElement"
  77. `(:raw "postAutocomplete")))
  78. (when spinner?
  79. (default-option "indicator" (string+ id "_spin"))))
  80. (unless completions-url
  81. (assert completions-generator)
  82. (setq completions-url
  83. (ajax-continuation (:args (prefix) :keep t :name "ac_completions")
  84. (let ((completions (funcall completions-generator prefix)))
  85. (html
  86. ((:ul :if* (> (length completions) scroll?) :style "height:500px;overflow:scroll;")
  87. (dolist (completion completions)
  88. (html
  89. ((:li :id (car completion))
  90. (if embedded-html
  91. (html (:princ (cdr completion)))
  92. (html (:princ-safe (cdr completion)))))))))))))
  93. (if textarea
  94. (html ((:textarea :id id :name name :do* input-options)
  95. (if value (html (:princ-safe value)))))
  96. (html ((:input :id id :name name :if* value :value value :do* input-options))))
  97. (if spinner?
  98. (html
  99. ((:img :src "/wupub/images/spinner.gif" :id (string+ id "_spin") :style "display:none;"))))
  100. (render-scripts
  101. ;; put the autocomplete div somewhere where it won't get clipped
  102. (:insert :bottom "body"
  103. (html ((:div :id update :class "auto_complete"))))
  104. ;; this complex tangle enables an action to be taken when a completion is selected.
  105. (:js (if on-selected (format nil "setupAutocomplete('~A', '~A');" id
  106. (ajax-continuation (:args (value value_string id) :name "ac_finish" :keep t)
  107. (funcall on-selected value value_string id)
  108. ))))
  109. (:js (format nil "var ~A_auto_completer = new Ajax.Autocompleter('~A', '~A', '~A', ~A);"
  110. id
  111. id
  112. update
  113. completions-url
  114. (json:encode-json-to-string (or options :empty-dict)))))
  115. )
  116. ;;; In-place editor (see http://madrobby.github.com/scriptaculous/ajax-inplaceeditor/ )
  117. ;;; :options alist of options as defined by the underlying widget
  118. ;;; :on-change function called with new value.
  119. ;;; :editable? nil to turn off editing.
  120. (defun in-place-field (&key (id (string (gensym "id")))
  121. name
  122. options
  123. (prompt "Click to edit.")
  124. on-change
  125. value
  126. class
  127. submit-on-blur?
  128. (editable? t)
  129. )
  130. (when prompt
  131. (push `("emptyText" . ,prompt) options))
  132. (when submit-on-blur?
  133. (push `("submitOnBlur" . "true") options))
  134. (let ((current-value value))
  135. (html
  136. :newline
  137. ((:span :id id :if* name :name name :if* class :class class); :style "border:1px solid gray"
  138. (if current-value
  139. (html (:princ current-value)))) ;was :princ-safe, but this lets you use html markup
  140. (when editable?
  141. (render-scripts
  142. (:js (format nil "new Ajax.InPlaceEditorWithEmptyText('~A', '~A', ~A);"
  143. id
  144. ;; :keep t permits multiple editings.
  145. (ajax-continuation (:args (value) :content-type "text/text" :name "inplace" :keep t)
  146. (when on-change (funcall on-change value))
  147. ;; you are supposed to send the value back as the body
  148. (write-string value *html-stream*))
  149. (json:encode-json-to-string (or options :empty-dict)))))))))
  150. ;;; A convenience for the simple case of a setfable field
  151. ;;; Here, on-change has different semantics, it's just a code snippet.
  152. (defmacro in-place-setf-field (object accessor &rest all-keys &key on-change &allow-other-keys)
  153. `(in-place-field :value (,accessor ,object)
  154. :on-change #'(lambda (v)
  155. (setf (,accessor ,object) v)
  156. ,on-change
  157. )
  158. ,@(delete-keyword-args '(:value :on-change) all-keys)
  159. ))