Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
  • 7 commits
  • 4 files changed
  • 0 comments
  • 2 contributors
Jul 13, 2011
Martin Sander AJAX frontend for problem page
This prevents re-loading of the side when running the code on the
problem page, it uses an ajax-request instead.

The sandbar.stateful-session is misused a bit in order to minimally
affect existing code. An alternative would be to make "run-code",
"mark-completed" and some other functions more functional and move all
the side effects (if needed) into "static-run-code" and "rest-run-code".

The animation on the frontend while the code is executing is not too
pretty, it is just meant as a start.
86ab7a4
Jul 16, 2011
Alan Malloy amalloy Merge pull request #83 from marvinthepa/ajax-frontend
AJAX frontend for problem page
a43a773
Alan Malloy amalloy whitespace 2c8824c
Alan Malloy amalloy Version number for release 9edf1b4
Alan Malloy amalloy Merge branch 'release/1.1.0' into develop 5e5d687
Alan Malloy amalloy Display better suggestions for next problem to tackle, as mdeboard pr…
…oposed.
04fee1c
Alan Malloy amalloy Multimethods are great, but the previous definition was a total abuse…
…. Refactor to use multiple function bodies instead.
1153525
2  project.clj
... ... @@ -1,4 +1,4 @@
1   -(defproject foreclojure "1.0.5"
  1 +(defproject foreclojure "1.1.0"
2 2 :description "4clojure - a website for lisp beginners"
3 3 :dependencies [[clojure "1.2.1"]
4 4 [clojure-contrib "1.2.0"]
66 resources/public/script/foreclojure.js
@@ -74,13 +74,73 @@ function configureCodeBox(){
74 74
75 75 var ClojureMode = require("ace/mode/clojure").Mode;
76 76 var session = editor.getSession();
  77 +
  78 + var clickHandler = function() {
  79 + var text = session.getValue(),
  80 + id = $('#id').attr("value"),
  81 + images = $(".testcases").find("img"),
  82 + cont = true,
  83 + high = false,
  84 + time = 800,
  85 +
  86 + beforeSendCallback = function(data) {
  87 + $("#message-text").text("Executing unit tests...");
  88 + var anim = function() {
  89 + if(cont) {
  90 + images.animate({
  91 + opacity: high ? 1.0 : 0.1,
  92 + }, time);
  93 + high = !high;
  94 + setTimeout(anim,time);
  95 + }
  96 + };
  97 + anim();
  98 + },
  99 + successCallback = function(data) {
  100 + var failingTest = data.failingTest;
  101 + cont = false;
  102 +
  103 + images.stop(true);
  104 + images.css({ opacity: 1.0, });
  105 + images.each( function(index,element) {
  106 + var color = "blue";
  107 + if (index < failingTest) {
  108 + color = "green";
  109 + } else if (index === failingTest) {
  110 + color = "red";
  111 + }
  112 + element.src = "/images/"+color+"light.png";
  113 + });
  114 +
  115 + $("#message-text").html(data.message);
  116 + $("#golfgraph").html(data.golfChart);
  117 + $("#golfscore").html(data.golfScore);
  118 + configureGolf();
  119 + };
  120 +
  121 + $.ajax({type: "POST",
  122 + url: "/rest/problem/"+id,
  123 + dataType: "json",
  124 + data: { id: id, code: text, },
  125 + timout: 20000, // default clojail timeout is 10000
  126 + beforeSend: beforeSendCallback,
  127 + success: successCallback,
  128 + error: function(data, str, error) {
  129 + $("#message-text").text("An Error occured: "+error);
  130 + },
  131 + });
  132 + return false;
  133 + };
  134 +
  135 + $("#run-button").click(clickHandler);
  136 +
77 137 session.setMode(new ClojureMode());
78 138 session.setUseSoftTabs(true);
79 139 session.setTabSize(2);
80 140
81 141 document.getElementById('editor').style.fontSize='13px';
82 142 $("#run-button").click(function(){
83   - var text = editor.getSession().getValue();
  143 + var text = editor.getSession().getValue();
84 144 $('#code').val(text);
85 145 });
86 146 }
@@ -99,10 +159,10 @@ function configureGolf(){
99 159 var text = $('#graph-link').html();
100 160 if (text && text == 'View Chart'){
101 161 $('#graph-link').html("View Code");
102   - }else{
  162 + } else {
103 163 $('#graph-link').html("View Chart");
104 164 }
105   -
  165 +
106 166
107 167 });
108 168
2  src/foreclojure/core.clj
@@ -41,4 +41,4 @@
41 41 (run-jetty (var app) {:join? false :port 8080}))
42 42
43 43 (defn -main [& args]
44   - (run))
  44 + (run))
64 src/foreclojure/problems.clj
@@ -10,7 +10,8 @@
10 10 (amalloy.utils [debug :only [?]]
11 11 [reorder :only [reorder]])
12 12 [amalloy.utils :only [defcomp]]
13   - compojure.core)
  13 + compojure.core
  14 + [clojure.contrib.json :only [json-str]])
14 15 (:require [sandbar.stateful-session :as session]
15 16 [clojure.string :as s]
16 17 (ring.util [response :as response])))
@@ -30,17 +31,33 @@
30 31 :where criteria
31 32 :sort {:_id 1}))))
32 33
33   -(defn next-unsolved-problem [solved-problems]
34   - (when-let [unsolved (->> (get-problem-list)
35   - (remove (comp (set solved-problems) :_id))
36   - (seq))]
37   - (apply min-key :_id unsolved)))
  34 +(defn next-unsolved-problem [solved-problems just-solved-id]
  35 + (when-let [unsolved (seq
  36 + (from-mongo
  37 + (fetch :problems
  38 + :only [:_id :title]
  39 + :where {:_id {:$nin solved-problems}}
  40 + :sort {:_id 1})))]
  41 + (let [[skipped not-yet-tried] (split-with #(< (:_id %) just-solved-id)
  42 + unsolved)]
  43 + (filter identity [(rand-nth (or (seq skipped)
  44 + [nil])) ; rand-nth barfs on empty seq
  45 + (first not-yet-tried)]))))
  46 +
  47 +(letfn [(problem-link [{id :_id title :title}]
  48 + (str "<a href='/problem/" id "'>" title "</a>"))]
  49 + (defn suggest-problems
  50 + ([] "You've solved them all! Come back later for more!")
  51 + ([problem]
  52 + (str "Now try " (problem-link problem) "!"))
  53 + ([skipped not-tried]
  54 + (str "Now move on to " (problem-link not-tried)
  55 + ", or go back and try " (problem-link skipped) " again!"))))
38 56
39 57 (defn next-problem-link [completed-problem-id]
40 58 (when-let [{:keys [solved]} (get-user (session/session-get :user))]
41   - (if-let [{:keys [_id title]} (next-unsolved-problem solved)]
42   - (str "Now try <a href='/problem/" _id "'>" title "</a>!")
43   - "You've solved them all! Come back later for more!")))
  59 + (apply suggest-problems
  60 + (next-unsolved-problem solved completed-problem-id))))
44 61
45 62 (defn get-recent-problems [n]
46 63 (map get-problem (map :_id (take-last n (get-problem-list)))))
@@ -119,9 +136,9 @@
119 136 (str "Congratulations, you've solved the problem!"
120 137 "<br />" (next-problem-link _id)))
121 138 :else (str "You've solved the problem! If you "
122   - (login-link "log in") " we can track your progress."))]
  139 + (login-link "log in" (str "/problem/" _id)) " we can track your progress."))]
123 140 (session/session-put! :code [_id code])
124   - (flash-msg (str message " " gist-link) (str "/problem/" _id))))
  141 + [(str message " " gist-link) (str "/problem/" _id)] ))
125 142
126 143 (def restricted-list '[use require in-ns future agent send send-off pmap pcalls])
127 144
@@ -145,7 +162,7 @@
145 162 (try
146 163 (let [user-forms (s/join " " (map pr-str (read-string-safely code)))]
147 164 (if (empty? user-forms)
148   - (flash-msg "Empty input is not allowed" *url*)
  165 + ["Empty input is not allowed" *url*]
149 166 (loop [[test & more] tests
150 167 i 0]
151 168 (session/flash-put! :failing-test i)
@@ -154,9 +171,13 @@
154 171 (let [testcase (s/replace test "__" user-forms)]
155 172 (if (sb sb-tester (first (read-string-safely testcase)))
156 173 (recur more (inc i))
157   - (flash-msg "You failed the unit tests." *url*)))))))
  174 + ["You failed the unit tests." *url*]
  175 + ))))))
158 176 (catch Exception e
159   - (flash-msg (.getMessage e) *url*)))))
  177 + [(.getMessage e) *url*]))))
  178 +
  179 +(defn static-run-code [id raw-code]
  180 + (apply flash-msg (run-code id raw-code)))
160 181
161 182 (defn render-test-cases [tests]
162 183 [:table {:class "testcases"}
@@ -191,6 +212,13 @@
191 212 :onclick "return false"}
192 213 [:span#graph-link "View Chart"]]])))
193 214
  215 +(defn rest-run-code [id raw-code]
  216 + (let [[message url] (run-code id raw-code)]
  217 + (json-str {:failingTest (session/flash-get :failing-test)
  218 + :message message
  219 + :golfScore (html (render-golf-score))
  220 + :golfChart (html (render-golf-chart))})))
  221 +
194 222 (def-page code-box [id]
195 223 (let [{:keys [_id title tags description restricted tests approved user]}
196 224 (get-problem (Integer. id))]
@@ -216,7 +244,8 @@
216 244 [:div
217 245 [:div.message
218 246 [:span#message-text (session/flash-get :message)]]
219   - (render-golf-score)]
  247 + [:div#golfscore
  248 + (render-golf-score)]]
220 249 (form-to {:id "run-code"} [:post *url*]
221 250 [:br]
222 251 [:br]
@@ -412,7 +441,10 @@
412 441 (POST "/problem/reject" [id]
413 442 (reject-problem (Integer. id) "We didn't like your problem."))
414 443 (POST "/problem/:id" [id code]
415   - (run-code (Integer. id) code))
  444 + (static-run-code (Integer. id) code))
  445 + (POST "/rest/problem/:id" [id code]
  446 + {:headers {"Content-Type" "application/json"}}
  447 + (rest-run-code (Integer. id) code))
416 448 (GET "/problems/rss" [] (create-feed
417 449 "4Clojure: Recent Problems"
418 450 "http://4clojure.com/problems"

No commit comments for this range

Something went wrong with that request. Please try again.