PageRenderTime 50ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/cljs/src/views/feed.cljs

http://github.com/fmw/vix
ClojureScript | 399 lines | 335 code | 44 blank | 20 comment | 2 complexity | 9c7cff5a12f38db65b790d7113bd6523 MD5 | raw file
Possible License(s): Apache-2.0
  1. ;; cljs/src/views/feed.cljs: UI implementation for feed management.
  2. ;;
  3. ;; Copyright 2011, F.M. (Filip) de Waard <fmw@vix.io>.
  4. ;;
  5. ;; Licensed under the Apache License, Version 2.0 (the "License");
  6. ;; you may not use this file except in compliance with the License.
  7. ;; You may obtain a copy of the License at
  8. ;;
  9. ;; http://www.apache.org/licenses/LICENSE-2.0
  10. ;;
  11. ;; Unless required by applicable law or agreed to in writing, software
  12. ;; distributed under the License is distributed on an "AS IS" BASIS,
  13. ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. ;; See the License for the specific language governing permissions and
  15. ;; limitations under the License.
  16. (ns vix.views.feed
  17. (:require [vix.core :as core]
  18. [vix.document :as document]
  19. [vix.util :as util]
  20. [vix.ui :as ui]
  21. [clojure.set :as set]
  22. [clojure.string :as string]
  23. [vix.templates.feed :as tpl]
  24. [goog.global :as global]
  25. [goog.events :as events]
  26. [goog.events.EventType :as event-type]
  27. [goog.dom :as dom]
  28. [goog.dom.classes :as classes]
  29. [goog.Uri :as Uri]))
  30. (def default-slug-has-invalid-chars-err
  31. (str "Slugs can only contain '/', '-', '.', alphanumeric characters "
  32. "and tokens (e.g. {day} and {document-title})."))
  33. (def default-slug-has-consecutive-dashes-or-slashes-err
  34. "Slugs shouldn't contain any consecutive '-' or '/' characters.")
  35. (def default-slug-initial-slash-required-err
  36. "The default slug needs to start with a '/'.")
  37. (def default-slug-requires-document-title-err
  38. "The default slug needs to include a {document-title} token.")
  39. (def default-slug-has-invalid-tokens-err
  40. "The following slug tokens aren't recognized: ")
  41. (def default-slug-has-unbalanced-braces-err
  42. "The default slug contains unbalanced '{' and '}' characters.")
  43. (def feed-name-required-err
  44. "The feed name value is required.")
  45. (def feed-title-required-err
  46. "The feed title value is required.")
  47. (def feed-name-has-invalid-characters-err
  48. "The feed name value can only contain '-' and alphanumeric characters.")
  49. (def feed-name-only-allows-dashes-in-body-err
  50. "The feed name needs to start and end with an alphanumeric character.")
  51. (def feed-name-has-consecutive-dashes-err
  52. "The feed name shouldn't contain any consecutive '-' characters.")
  53. (def could-not-save-feed-err
  54. "Something went wrong while trying to save this feed.")
  55. (defn display-document-list [main-el xhr]
  56. (ui/render-template main-el
  57. tpl/list-documents
  58. {:json (. xhr (getResponseJson))}))
  59. (defn display-feed-list [main-el xhr]
  60. (let [feeds (. xhr (getResponseJson))]
  61. (ui/render-template main-el
  62. tpl/list-feeds
  63. {:feeds feeds
  64. :languages (set (map #("language-full" %)
  65. (js->clj feeds)))})))
  66. (defn list-documents [language feed-name]
  67. (util/set-page-title! (str "List of documents for feed \"" feed-name "\""))
  68. (document/get-documents-for-feed
  69. language
  70. feed-name
  71. (fn [e]
  72. (let [main-el (dom/getElement "main-page")
  73. xhr (.target e)
  74. status (. xhr (getStatus))]
  75. (if (= status 200)
  76. (do
  77. (display-document-list main-el xhr)
  78. (create-document-list-events language feed-name))
  79. (ui/render-template main-el tpl/list-documents-error))))))
  80. (defn list-feeds-callback [e]
  81. (let [main-el (dom/getElement "main-page")
  82. xhr (.target e)
  83. status (. xhr (getStatus))]
  84. (if (= status 200)
  85. (do
  86. (display-feed-list main-el xhr)
  87. (create-feed-list-events))
  88. (ui/render-template main-el tpl/list-feeds-error))))
  89. (defn list-feeds []
  90. (util/set-page-title! "Feeds overview")
  91. (document/get-feeds-list list-feeds-callback))
  92. (defn delete-doc-callback [language feed-name e]
  93. (list-documents language feed-name))
  94. (defn create-document-list-events [language feed-name]
  95. (core/xhrify-internal-links! (core/get-internal-links!))
  96. (events/listen (dom/getElement "add-document")
  97. "click"
  98. (fn [e]
  99. (core/navigate (str language "/" feed-name "/new")
  100. "New Document")))
  101. (ui/trigger-on-class "delete-document"
  102. "click"
  103. (fn [e]
  104. (. e (preventDefault))
  105. (let [slug (nth
  106. (string/split (.id (.target e)) "_") 2)]
  107. (document/delete-doc slug
  108. (partial delete-doc-callback
  109. language
  110. feed-name))))))
  111. (defn create-feed-list-events [feed]
  112. (core/xhrify-internal-links! (core/get-internal-links!))
  113. (events/listen (dom/getElement "add-feed")
  114. "click"
  115. (fn [e]
  116. (core/navigate "new-feed") "New Feed"))
  117. ; converting to vector to avoid issues with doseq and arrays
  118. (doseq [delete-link (cljs.core.Vector/fromArray
  119. (dom/getElementsByTagNameAndClass "a" "delete-feed"))]
  120. (events/listen delete-link
  121. "click"
  122. (fn [e]
  123. (. e (preventDefault))
  124. (let [id-segments (string/split (.id (.target e)) ":")
  125. language (nth id-segments 1)
  126. feed-name (nth id-segments 2)]
  127. (document/delete-feed language
  128. feed-name
  129. list-feeds))))))
  130. (defn get-invalid-tokens [slug]
  131. (set/difference (set (re-seq #"\{[^\}]{0,}}" slug))
  132. #{"{language}"
  133. "{day}"
  134. "{month}"
  135. "{year}"
  136. "{document-title}"
  137. "{feed-name}"
  138. "{ext}"}))
  139. (defn has-unbalanced-braces? [slug]
  140. (let [braces (re-seq #"[\{\}]" slug)]
  141. (if (odd? (count braces))
  142. true
  143. (pos? (count (filter #(not (= ["{" "}"] %)) (partition 2 braces)))))))
  144. (defn validate-default-slug [e]
  145. (let [status-el (dom/getElement "status-message")
  146. slug-el (.target e)
  147. slug (.value slug-el)
  148. slug-label-el (dom/getElement "default-slug-format-select-label")
  149. err #(ui/display-error status-el % slug-el slug-label-el)
  150. invalid-tokens (get-invalid-tokens slug)]
  151. (cond
  152. (not (= (first slug) "/"))
  153. (err default-slug-initial-slash-required-err)
  154. (re-find #"[^/\-a-zA-Z0-9\{\}\.]" slug)
  155. (err default-slug-has-invalid-chars-err)
  156. (not (re-find #"\{document-title\}" slug))
  157. (err default-slug-requires-document-title-err)
  158. (has-unbalanced-braces? slug)
  159. (err default-slug-has-unbalanced-braces-err)
  160. (pos? (count invalid-tokens))
  161. (err (str default-slug-has-invalid-tokens-err
  162. (apply str (interpose ", " invalid-tokens))))
  163. (util/has-consecutive-dashes-or-slashes? slug)
  164. (err default-slug-has-consecutive-dashes-or-slashes-err)
  165. :else (ui/remove-error status-el slug-el slug-label-el))))
  166. (defn validate-feed-name-and-preview-in-slug [e]
  167. (let [name-el (.target e)
  168. name-val (.value name-el)
  169. name-label-el (dom/getElement "name-label")
  170. status-el (dom/getElement "status-message")
  171. dsfs-el (dom/getElement "default-slug-format-select")
  172. err #(ui/display-error status-el % name-el name-label-el)]
  173. (cond
  174. (string/blank? name-val)
  175. (err feed-name-required-err)
  176. (not (re-matches #"[\-a-zA-Z0-9]+" name-val))
  177. (err feed-name-has-invalid-characters-err)
  178. (or (= (first name-val) "-") (= (last name-val) "-"))
  179. (err feed-name-only-allows-dashes-in-body-err)
  180. (util/has-consecutive-dashes-or-slashes? name-val)
  181. (err feed-name-has-consecutive-dashes-err)
  182. :else
  183. (ui/remove-error status-el name-el name-label-el))
  184. (preview-slug!)))
  185. (defn preview-slug! []
  186. (let [dsfs-el (dom/getElement "default-slug-format-select")
  187. select-opts (cljs.core.Vector/fromArray (dom/getChildren dsfs-el))]
  188. (when-not (classes/has (dom/getElement "name") "error")
  189. (doseq [opt select-opts]
  190. (dom/setTextContent opt (util/create-slug (.value opt)
  191. "document-title"
  192. (get-feed-value-map!)
  193. (util/date-now!)
  194. "ext"))))))
  195. (defn validate-feed! []
  196. (let [name-el (dom/getElement "name")
  197. title-el (dom/getElement "title")
  198. subtitle-el (dom/getElement "subtitle")
  199. dsf-el (dom/getElement "default-slug-format")
  200. ddt-el (dom/getElement "default-document-type")
  201. status-el (dom/getElement "status-message")
  202. err (partial ui/display-error status-el)]
  203. (cond
  204. (string/blank? (.value name-el))
  205. (err feed-name-required-err
  206. name-el
  207. (dom/getElement "name-label"))
  208. (string/blank? (.value title-el))
  209. (err feed-title-required-err
  210. (dom/getElement "title")
  211. (dom/getElement "title-label"))
  212. :else
  213. (if (classes/has status-el "error")
  214. false
  215. true))))
  216. (defn get-feed-value-map! []
  217. (let [language (util/pair-from-string (ui/get-form-value "language"))]
  218. {:name (ui/get-form-value "name")
  219. :title (ui/get-form-value "title")
  220. :subtitle (ui/get-form-value "subtitle")
  221. :language (first language)
  222. :language-full (last language)
  223. :default-slug-format (ui/get-form-value "default-slug-format")
  224. :default-document-type (ui/get-form-value "default-document-type")
  225. }))
  226. ; FIXME: avoid duplication between this and the other 3 xhr callback fns
  227. (defn save-new-feed-xhr-callback [e]
  228. (let [xhr (.target e)]
  229. (if (= (. xhr (getStatus)) 201)
  230. (let [json (js->clj (. xhr (getResponseJson)))]
  231. (core/navigate-replace-state (str "edit-feed/"
  232. ("language" json)
  233. "/"
  234. ("name" json))
  235. (str "Edit feed \""
  236. ("name" json)
  237. "\"")))
  238. (ui/display-error (dom/getElement "status-message")
  239. could-not-save-feed-err))))
  240. ; FIXME: avoid duplication between this and the other 3 xhr callback fns
  241. (defn save-existing-feed-xhr-callback [e]
  242. (let [xhr (.target e)]
  243. (if (= (. xhr (getStatus)) 200)
  244. (let [json (js->clj (. xhr (getResponseJson)))]
  245. (core/navigate-replace-state (str "edit-feed/"
  246. ("language" json)
  247. "/"
  248. ("name" json))
  249. (str "Edit feed \""
  250. ("name" json)
  251. "\"")))
  252. (ui/display-error (dom/getElement "status-message")
  253. could-not-save-feed-err))))
  254. (defn create-feed-form-events [status language feed-name]
  255. (let [dsf-el (dom/getElement "default-slug-format")
  256. title-el (dom/getElement "title")]
  257. (events/listen (dom/getElement "default-slug-format-select")
  258. event-type/CHANGE
  259. (fn [e]
  260. (let [val (.value (.target e))]
  261. (if (= val "custom")
  262. (do
  263. (ui/enable-element dsf-el))
  264. (do
  265. (ui/disable-element dsf-el)
  266. (ui/set-form-value dsf-el val))))))
  267. (events/listen dsf-el event-type/INPUT validate-default-slug)
  268. (events/listen (dom/getElement "name")
  269. event-type/INPUT
  270. validate-feed-name-and-preview-in-slug)
  271. ; remove outdated errors left by save event validation
  272. (events/listen title-el
  273. event-type/INPUT
  274. (fn [e]
  275. (when-not (string/blank? (.value title-el))
  276. (ui/remove-error (dom/getElement "status-message")
  277. (dom/getElement "title-label")
  278. title-el))))
  279. (events/listen (dom/getElement "save-feed")
  280. event-type/CLICK
  281. (fn [e]
  282. (when (validate-feed!)
  283. (if (= :new status)
  284. (document/create-feed
  285. save-new-feed-xhr-callback
  286. (get-feed-value-map!))
  287. (document/update-feed
  288. language
  289. feed-name
  290. save-existing-feed-xhr-callback
  291. (get-feed-value-map!))))))))
  292. (defn render-feed-form [feed-data]
  293. (ui/render-template (dom/getElement "main-page") tpl/manage-feed feed-data)
  294. (core/xhrify-internal-links! (core/get-internal-links!))
  295. (when (:language feed-data)
  296. (ui/set-form-value (dom/getElement "language") (:language feed-data))))
  297. (defn display-new-feed-form []
  298. (render-feed-form {:status "new"
  299. :name ""
  300. :title ""
  301. :subtitle ""
  302. :language "['en','English']"
  303. :default_slug_format
  304. "/{language}/{feed-name}/{document-title}"
  305. :default_document_type "standard"})
  306. (create-feed-form-events :new nil nil))
  307. (defn display-edit-feed-xhr-callback [language feed-name e]
  308. (let [xhr (.target e)
  309. status (. xhr (getStatus))]
  310. (if (= status 200)
  311. (let [json (js->clj (. xhr (getResponseJson)))]
  312. (util/set-page-title!
  313. (str "Edit feed \"" ("title" json) "\""))
  314. (render-feed-form {:status "edit"
  315. :name ("name" json)
  316. :title ("title" json)
  317. :subtitle (or ("subtitle" json) "")
  318. :language (str "['"
  319. ("language" json)
  320. "','"
  321. ("language-full" json)
  322. "']")
  323. :default_slug_format ("default-slug-format" json)
  324. :default_document_type
  325. ("default-document-type" json)})
  326. (preview-slug!)
  327. (let [dsf ("default-slug-format" json)
  328. select-option (partial ui/set-form-value
  329. (dom/getElement
  330. "default-slug-format-select"))]
  331. (cond
  332. (= dsf "/{language}/{feed-name}/{document-title}")
  333. (select-option "/{language}/{feed-name}/{document-title}")
  334. (= dsf "/{language}/{feed-name}/{document-title}.{ext}")
  335. (select-option "/{language}/{feed-name}/{document-title}.{ext}")
  336. (= dsf "/{language}/{document-title}")
  337. (select-option "/{language}/{document-title}")
  338. (= dsf "/{language}/{year}/{month}/{day}/{document-title}")
  339. (select-option
  340. "/{language}/{year}/{month}/{day}/{document-title}")
  341. :else
  342. (do
  343. (select-option "custom")
  344. (ui/enable-element "default-slug-format-select")
  345. (ui/enable-element "default-slug-format"))))
  346. (ui/disable-element "name")
  347. (create-feed-form-events :edit language feed-name))
  348. ; else clause
  349. (ui/render-template (dom/getElement "main-page") tpl/feed-not-found))))
  350. (defn display-edit-feed-form [language feed-name]
  351. (document/get-feed language
  352. feed-name
  353. #(display-edit-feed-xhr-callback language
  354. feed-name
  355. %)))