Skip to content

Commit 2e2ff6f

Browse files
committed
More organization for the trace input form.
I rearranged the state management of the form to use Bacon.js more centrally, and added a 'disabled' state to the OK button when the form is invalid. Also a couple of assorted small fixes like moving common.js to the common folder, and adding the missing 'header-push' div to the traces page.
1 parent 47e8f44 commit 2e2ff6f

File tree

5 files changed

+103
-62
lines changed

5 files changed

+103
-62
lines changed

codepulse/src/main/resources/toserve/pages/traces/common.js renamed to codepulse/src/main/resources/toserve/common/common.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,19 @@
2424
Bacon.Observable.prototype.noLazy = function(){
2525
this.onValue(function(){})
2626
return this
27+
}
28+
29+
Bacon.Model = function(initial){
30+
var value = initial
31+
var changes = new Bacon.Bus()
32+
var prop = changes.toProperty(value).noLazy()
33+
34+
this.get = function(){ return value }
35+
this.set = function(v){
36+
value = v
37+
changes.push(value)
38+
}
39+
40+
this.__defineGetter__('changes', function(){ return changes })
41+
this.__defineGetter__('property', function(){ return prop })
2742
}

codepulse/src/main/resources/toserve/pages/TraceInputForm/TraceInputForm.js

Lines changed: 83 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ $(document).ready(function(){
4242
/*
4343
* Set the current state of the form (`name` and `fileData`) to null, initially.
4444
*/
45-
var currentName = null,
46-
currentFileData = null
45+
var traceFile = new Bacon.Model(null)
46+
var traceName = new Bacon.Model('')
4747

4848
/*
4949
* Set the `currentFeedback` to an empty object. As errors and warnings w.r.t.
@@ -57,9 +57,12 @@ $(document).ready(function(){
5757
cancelButton.click(clearForm)
5858

5959
/*
60-
* Submit the form when the submit button is clicked.
60+
* Typing in the nameInput field will update the `traceName`.
6161
*/
62-
submitButton.click(submitForm)
62+
nameInput.asEventStream('input').onValue(function(){
63+
var value = nameInput.val().trim()
64+
traceName.set(value)
65+
})
6366

6467
/*
6568
* Set up the fileInput using the jQuery file upload plugin.
@@ -72,20 +75,12 @@ $(document).ready(function(){
7275
add: function(e, data){
7376
// use this `data` as the current file data.
7477
// this will be used once the form is submitted.
75-
currentFileData = data
76-
77-
// get the file's name and put it in the fileChoiceLabel
78-
var filename = data.files[0].name
79-
fileChoiceLabel.text(filename)
80-
81-
if(!currentName){
82-
currentName = filename
83-
nameInput.val(currentName).trigger('input')
84-
}
78+
traceFile.set(data)
8579
},
8680
formData: function(){
87-
if(!currentName) return []
88-
else return [{name: 'name', value: currentName}]
81+
var name = traceName.get()
82+
if(!name) return []
83+
else return [{name: 'name', value: name}]
8984
},
9085
done: function(e, data){
9186
onSubmitDone(data.result)
@@ -95,17 +90,36 @@ $(document).ready(function(){
9590
}
9691
})
9792

93+
var traceFileName = traceFile.property.map(function(data){
94+
if(data && data.files && data.files.length){
95+
return data.files[0].name
96+
} else {
97+
return null
98+
}
99+
})
100+
101+
traceFileName.onValue(function(filename){
102+
fileChoiceLabel.text(filename || fileChoiceOriginalText)
103+
})
104+
105+
traceFileName.onValue(function(filename){
106+
if(filename && !traceName.get() && !nameOptional){
107+
traceName.set(filename)
108+
nameInput.val(filename)
109+
}
110+
})
111+
98112
/*
99113
* Alternate code path to sending the data to the server, used
100114
* when Code Pulse is being run as an embedded node-webkit app.
101-
* It sends the `path` anc `currentName` to the server, assuming
115+
* It sends the `path` and `traceName` to the server, assuming
102116
* that the server is on the same machine, so it has read access
103117
* to the file at that path.
104118
*/
105119
function doNativeUpload(path){
106120
console.log('using native upload behavior on ', path)
107121
$.ajax(uploadUrl, {
108-
data: {'path': path, name: currentName},
122+
data: {'path': path, name: traceName.get()},
109123
type: 'POST',
110124
error: function(xhr, status){ onSubmitError(xhr.responseText) },
111125
success: function(data){ onSubmitDone(data) }
@@ -190,21 +204,15 @@ $(document).ready(function(){
190204
*/
191205
var nameInputValues = nameInput.asEventStream('input')
192206
.map(function(){ return nameInput.val().trim() })
193-
194-
/*
195-
* Assign the latest change from the nameInput to the currentName
196-
*/
197-
nameInputValues
198-
.onValue(function(name){
199-
currentName = name
200-
})
207+
.toProperty()
208+
.noLazy()
201209

202210
/*
203211
* As the name changes, ask the server if the latest name
204212
* would conflict with the name of an existing trace. Set
205213
* the 'name-conflict' feedback warning accordingly.
206214
*/
207-
nameInputValues
215+
traceName.changes
208216
.debounce(300)
209217
.flatMapLatest(function(name){
210218
return Bacon.fromNodeCallback(function(callback){
@@ -224,7 +232,7 @@ $(document).ready(function(){
224232
* error if the name becomes blank after the user had already
225233
* typed something (e.g. they deleted what they typed).
226234
*/
227-
if(!nameOptional) nameInputValues
235+
if(!nameOptional) traceName.changes
228236
.skipWhile(function(name){ return !name.length })
229237
.map(function(name){ return !name.length })
230238
.onValue(function(isEmpty){
@@ -236,38 +244,58 @@ $(document).ready(function(){
236244
})
237245

238246
/*
239-
* Return whether or not the form can be submitted.
240-
* There must be a file chosen, and there must be a name
241-
* filled in unless the name is optional.
247+
* Combine the various form inputs as an object
248+
* in a baconjs Property.
242249
*/
243-
function canSubmitForm(){
244-
var hasValidName = nameOptional || (currentName && currentName.trim())
245-
console.log(hasValidName, currentFileData)
246-
return hasValidName && currentFileData
250+
var submissionCriteria = Bacon.combineTemplate({
251+
name: traceName.property,
252+
fileData: traceFile.property
253+
})
254+
255+
/*
256+
* Check a submissionCriteria object to see if it
257+
* should be allowed for submission.
258+
*/
259+
function checkSubmissionCriteria(criteria){
260+
var hasValidName = nameOptional || criteria.name
261+
return hasValidName && criteria.fileData
247262
}
248263

249264
/*
250-
* Begin the upload process, using either 'native' or regular
251-
* browser behavior depending on whether CodePulse is being run
252-
* as an embedded node-webkit app or in a regular browser.
265+
* Represent the current form state as whether or not it can
266+
* be submitted.
253267
*/
254-
function submitForm(){
255-
if(!canSubmitForm()){
256-
alert("You can't do that yet")
257-
return
258-
}
268+
var canSubmitForm = submissionCriteria.map(checkSubmissionCriteria)
259269

260-
var file = currentFileData.files[0],
261-
filepath = file.path
270+
/*
271+
* Set the 'disabled' class on the submit button depending on the
272+
* state of the `canSubmitForm` property.
273+
*/
274+
canSubmitForm.not().assign(submitButton, 'toggleClass', 'disabled')
262275

263-
if(CodePulse.isEmbedded && filepath){
264-
// native upload behavior
265-
doNativeUpload(filepath)
266-
} else {
267-
// browser upload behavior
268-
currentFileData.submit()
269-
}
270-
}
276+
/*
277+
* When the submit button is clicked, if the form is allowed to be
278+
* submitted, do the submission. Depending on whether CodePulse is
279+
* being run in a browser or as an embedded node-webkit app, the
280+
* actual submission mechanism will be different.
281+
*/
282+
submissionCriteria
283+
.sampledBy(submitButton.asEventStream('click'))
284+
.filter(canSubmitForm)
285+
.onValue(function(criteria){
286+
console.log('allow submission with ', criteria)
287+
288+
var file = criteria.fileData.files[0],
289+
filepath = file.path
290+
291+
if(CodePulse.isEmbedded && filepath){
292+
// native upload behavior
293+
doNativeUpload(filepath)
294+
} else {
295+
// browser upload behavior
296+
criteria.fileData.submit()
297+
}
298+
})
271299

272300
/*
273301
* Ask the server if there will be a name conflict with the given `name`.
@@ -299,8 +327,6 @@ $(document).ready(function(){
299327
var feedbackObj = message ? {message: message, type: type} : null
300328
currentFeedback[category] = feedbackObj
301329

302-
console.log(currentFeedback)
303-
304330
var nameFeedbacks = [
305331
currentFeedback['name-conflict'],
306332
currentFeedback['name-empty']
@@ -336,12 +362,11 @@ $(document).ready(function(){
336362
* and resetting the input and feedback UI elements.
337363
*/
338364
function clearForm(){
339-
currentName = null
340-
currentFileData = null
365+
traceName.set('')
366+
traceFile.set(null)
341367
currentFeedback = {}
342368

343369
nameInput.val('')
344-
fileChoiceLabel.text(fileChoiceOriginalText)
345370
updateFeedbackUI(nameFeedbackArea, [])
346371
updateFeedbackUI(fileFeedbackArea, [])
347372
}

codepulse/src/main/scala/com/secdec/codepulse/components/includes/snippet/Includes.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ trait IncludesRegistry {
4848

4949
/** A Javascript Dependency, rendered as a `<script>` tag */
5050
case class JS(src: String) extends Dependency {
51-
lazy val asXml = <script class="lift:with-resource-id lift:head" type="text/javascript" src={ "/" + LiftRules.resourceServerPath + "/" + src } lift:eagereval="true"/>
51+
lazy val asXml = <script class="lift:with-resource-id lift:head" type="text/javascript" src={ "/" + LiftRules.resourceServerPath + "/" + src }/>
5252
}
5353

5454
/** A CSS Dependency, rendered as a stylesheet `<link>` */
@@ -107,7 +107,7 @@ object Includes extends DispatchSnippet with IncludesRegistry {
107107
/*
108108
* Hand-crafted-with-love dependencies:
109109
*/
110-
110+
val commonJs = register("commonJs", JS("common/common.js"))
111111
val overlay = register("overlay", spinner, JS("widgets/overlay/overlay.js"), CSS("widgets/overlay/overlay.css"))
112112
val commonStyle = register("commonStyle", CSS("common/common.css"))
113113
val desktopStyle = register("desktopStyle", CSS("common/desktop.css"))
@@ -122,7 +122,6 @@ object Includes extends DispatchSnippet with IncludesRegistry {
122122
val traceInputForm = register("TraceInputForm", CSS("pages/TraceInputForm/TraceInputForm.css"), JS("pages/TraceInputForm/TraceInputForm.js"))
123123

124124
val tracesPage = register("tracesPage",
125-
JS("pages/traces/common.js"),
126125
traceAPI,
127126
JS("pages/traces/TraceDataUpdates.js"),
128127
JS("pages/traces/TraceStatus.js"),

codepulse/src/main/webapp/templates-hidden/desktop.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<script class="lift:Includes.jquery"></script>
88
<script class="lift:Includes.bootstrap"></script>
99
<script class="lift:Includes.baconjs"></script>
10+
<script class="lift:Includes.commonJs"></script>
1011
<link class="lift:Includes.FontAwesome"></link>
1112
<link class="lift:Includes.commonStyle"></link>
1213
<link class="lift:Includes.desktopStyle"></link>

codepulse/src/main/webapp/traces.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
<lift:surround with="desktop" at="content">
22
<lift:head>
33
<link class="lift:Includes.d3"></link>
4-
<link class="lift:Includes.baconjs"></link>
54
<link class="lift:Includes.codetreemap"></link>
65
<link class="lift:Includes.tracesPage"></link>
76
</lift:head>
87

98
<div class="lift:TraceWidgetry">
109
<trace:stateupdates></trace:stateupdates>
1110

11+
<div class="header-push"></div>
12+
1213
<div id="fixed-subheader" class="titlearea">
1314
<div class="wscope">
1415
<trace:nameconflict>

0 commit comments

Comments
 (0)