quick_script 0.0.47

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,695 @@
1
+ @initKO = ->
2
+ ko.bindingHandlers.fadeVisible =
3
+ init : (element, valueAccessor) ->
4
+ shouldDisplay = ko.utils.unwrapObservable(valueAccessor())
5
+ if shouldDisplay then $(element).show() else $(element).hide()
6
+ update : (element, value) ->
7
+ shouldDisplay = value()
8
+ if shouldDisplay then $(element).fadeIn('slow') else $(element).hide()
9
+
10
+ ko.bindingHandlers.slideVisible =
11
+ init : (element, valueAccessor) ->
12
+ shouldDisplay = ko.utils.unwrapObservable(valueAccessor())
13
+ if shouldDisplay then $(element).show() else $(element).hide()
14
+ update : (element, valueAccessor) ->
15
+ shouldDisplay = ko.utils.unwrapObservable(valueAccessor())
16
+ if shouldDisplay then $(element).slideDown('slow') else $(element).slideUp()
17
+
18
+ ko.bindingHandlers.handleEnter =
19
+ init : (element, valueAccessor, bindingsAccessor, viewModel) ->
20
+ $(element).keypress (ev)->
21
+ if (ev.keyCode == 13)
22
+ action = valueAccessor()
23
+ val = bindingsAccessor().value
24
+ val($(element).val())
25
+ action.call(viewModel)
26
+ return false
27
+
28
+ ko.bindingHandlers.validate =
29
+ init : (element, valueAccessor) ->
30
+ opts = valueAccessor()
31
+ $(element).blur ->
32
+ if opts.test()
33
+ $(element).removeClass(opts.err_css)
34
+ $(element).addClass(opts.ok_css)
35
+ else
36
+ $(element).removeClass(opts.ok_css)
37
+ $(element).addClass(opts.err_css)
38
+ opts.on_err() if opts.on_err?
39
+
40
+ ko.bindingHandlers.cropImage =
41
+ init : (element, valueAccessor) ->
42
+ opts = valueAccessor()
43
+ $(element).css
44
+ background : 'url(' + ko.utils.unwrapObservable(opts[0]) + ')',
45
+ backgroundSize: 'cover',
46
+ 'background-position': 'center',
47
+ backgroundColor: '#FFF',
48
+ width: opts[1],
49
+ height: opts[2],
50
+ display: 'inline-block'
51
+
52
+ ko.bindingHandlers.tinymce =
53
+ init : (element, valueAccessor, bindingsAccessor, viewModel) ->
54
+ options = {
55
+ width : $(element).width(),
56
+ height : $(element).height(),
57
+ content_css : '/assets/screen/tinymce.css',
58
+ theme : 'advanced',
59
+ theme_advanced_toolbar_location : 'top',
60
+ theme_advanced_buttons1 : 'bold, italic, underline, separator, undo, redo, separator, bullist, numlist, blockquote, separator, justifyleft, justifycenter, justifyright, separator, image, link, unlink, separator, code',
61
+ theme_advanced_buttons2 : '',
62
+ theme_advanced_buttons3 : ''
63
+ }
64
+ val = valueAccessor()
65
+ options.setup = (ed) ->
66
+ ed.onChange.add (ed, l) ->
67
+ val(l.content)
68
+ # handle destroying an editor (based on what jQuery plugin does)
69
+ ko.utils.domNodeDisposal.addDisposeCallback element, ->
70
+ ed = tinyMCE.get(element.id)
71
+ if (ed)
72
+ ed.remove()
73
+ console.log('removing tinymce')
74
+
75
+ setTimeout ->
76
+ $(element).tinymce(options)
77
+ if ($(element).attr('name') != 'undefined')
78
+ ko.editors[$(element).attr('name')] = element.id
79
+ , 100
80
+ console.log('init tinymce')
81
+ update : (element, valueAccessor) ->
82
+ $(element).html(ko.utils.unwrapObservable(valueAccessor()))
83
+
84
+ ko.bindingHandlers.jsfileupload =
85
+ init : (element, valueAccessor, bindingsAccessor, viewModel) ->
86
+ model = valueAccessor()
87
+ $(element).fileupload(model.input.options)
88
+ $(element).change (evt)->
89
+ model.input.files(evt.target.files)
90
+ model.fileupload = $(element).fileupload.bind($(element))
91
+ model.selectFile = ->
92
+ $(element).click()
93
+
94
+ ko.bindingHandlers.fileupload =
95
+ init : (element, valueAccessor, bindingsAccessor, viewModel) ->
96
+ model = valueAccessor()
97
+ $(element).change (evt)->
98
+ model.input.files(evt.target.files)
99
+ model.selectFile = ->
100
+ $(element).click()
101
+
102
+ ko.bindingHandlers.calendar =
103
+ init : (element, valueAccessor, bindingsAccessor, viewModel) ->
104
+ $(element).fullCalendar('destroy')
105
+ $(element).fullCalendar(ko.utils.unwrapObservable(valueAccessor()))
106
+ viewModel.calendar = $(element).fullCalendar.bind($(element))
107
+
108
+ ko.bindingHandlers.center =
109
+ init : (element, valueAccessor, bindingsAccessor, viewModel) ->
110
+ setTimeout ->
111
+ $(element).center()
112
+ , 1
113
+
114
+ ko.bindingHandlers.progressbar =
115
+ update: (element, valueAccessor) ->
116
+ $(element).progressbar({value : ko.utils.unwrapObservable(valueAccessor())})
117
+
118
+ ko.bindingHandlers.placeholder =
119
+ init: (element, valueAccessor) ->
120
+ fn = ->
121
+ if ($(element).val().length > 0)
122
+ $(element).siblings('label').hide()
123
+ else
124
+ $(element).siblings('label').show()
125
+ $(element).live('blur change keyup', fn)
126
+ update: (element, valueAccessor) ->
127
+ if ($(element).val().length > 0)
128
+ $(element).siblings('label').hide()
129
+ else
130
+ $(element).siblings('label').show()
131
+
132
+
133
+ ko.absorbModel = (data, self) ->
134
+ for prop, val of data
135
+ continue if typeof(val) == "function"
136
+ if !self[prop]?
137
+ self[prop] = ko.observable(val)
138
+ else if (typeof(self[prop].handleData) == "function")
139
+ self[prop].handleData(val)
140
+ else
141
+ self[prop](val)
142
+ self.fields.pushOnce(prop)
143
+ self.model_state(ko.modelStates.READY)
144
+
145
+ ko.addFields = (fields, val, self) ->
146
+ for prop in fields
147
+ ko.addField prop, val, self
148
+
149
+ ko.addField = (field, val, valid_fn, self) ->
150
+ if !self?
151
+ self = valid_fn
152
+ valid_fn = null
153
+ if (typeof(self[field]) != "function")
154
+ if (val instanceof Array)
155
+ self[field] = ko.observableArray()
156
+ else
157
+ self[field] = ko.observable(val)
158
+
159
+ self["#{field}_valid"] = ko.computed( (-> (valid_fn.bind(self))(self[field]())), self) if valid_fn?
160
+ else
161
+ self[field](val)
162
+ if (typeof(field) == "string")
163
+ self.fields.pushOnce(field)
164
+
165
+ ko.addSubModel = (field, model, self) ->
166
+ if self[field]?
167
+ self[field].reset()
168
+ else
169
+ self[field] = new model({}, self)
170
+ self.fields.pushOnce(field) if typeof(field) == "string"
171
+
172
+ ko.intercepter = (observable, write_fn, self) ->
173
+ underlying_observable = observable
174
+ return ko.dependentObservable
175
+ read: underlying_observable,
176
+ write: (val) ->
177
+ if (val != underlying_observable())
178
+ write_fn.call(self, underlying_observable, underlying_observable(), val)
179
+
180
+ ko.dirtyFlag = (root, isInitiallyDirty) ->
181
+ result = ->
182
+ _initialState = ko.observable(ko.toJSON(root))
183
+ _isInitiallyDirty = ko.observable(isInitiallyDirty)
184
+
185
+ result.isDirty = ko.dependentObservable ->
186
+ return _isInitiallyDirty() || (_initialState() != ko.toJSON(root))
187
+
188
+ result.reset = ->
189
+ _initialState(ko.toJSON(root))
190
+ _isInitiallyDirty(false)
191
+
192
+ return result
193
+
194
+ ko.copyObject = (obj, fields) ->
195
+ ret = {}
196
+ for prop in fields
197
+ ret[prop] = obj[prop]
198
+ return ret
199
+
200
+ ko.modelStates = {}
201
+ ko.modelStates.READY = 1
202
+ ko.modelStates.LOADING = 2
203
+ ko.modelStates.SAVING = 3
204
+ ko.modelStates.EDITING = 4
205
+ ko.modelStates.INSERTING = 5
206
+ ko.modelStates.APPENDING = 6
207
+ ko.editors = {}
208
+
209
+ jQuery.fn.extend
210
+ to_s : ->
211
+ $('<div>').append(this.clone()).remove().html()
212
+ center : ->
213
+ this.css("position","absolute")
214
+ this.css("top", (($(window).height() - this.outerHeight()) / 2) + $(window).scrollTop() + "px")
215
+ this.css("left", (($(window).width() - this.outerWidth()) / 2) + $(window).scrollLeft() + "px")
216
+ return this
217
+ koBind : (viewModel) ->
218
+ this.each ->
219
+ ko.cleanNode(this)
220
+ ko.applyBindings(viewModel, this)
221
+ koClean : ->
222
+ this.each ->
223
+ ko.cleanNode(this)
224
+
225
+ jQuery.ajax_qs = (opts)->
226
+ data = new FormData()
227
+ req = new XMLHttpRequest()
228
+ for key, val of opts.data
229
+ data.append key, val
230
+ req.onreadystatechange = (ev)->
231
+ if req.readyState == 4
232
+ if req.status == 200
233
+ resp = eval("(" + req.responseText + ")")
234
+ opts.success(resp)
235
+ else
236
+ opts.error() if opts.error?
237
+ req.upload.addEventListener('error', opts.error) if opts.error?
238
+ req.upload.addEventListener('progress', opts.progress) if opts.progress?
239
+ req.open opts.type, opts.url, true
240
+ req.setRequestHeader 'X-CSRF-Token', jQuery('meta[name="csrf-token"]').attr('content')
241
+ req.send(data)
242
+ return req
243
+
244
+ class @Model
245
+ init : ->
246
+ extend : ->
247
+ constructor: (data, collection) ->
248
+ @fields = []
249
+ ko.addFields(['id'], '', this)
250
+ @events = {}
251
+ @adapter = new ModelAdapter()
252
+ @collection = collection
253
+ @db_state = ko.observable({})
254
+ @errors = ko.observable([])
255
+ @model_state = ko.observable(0)
256
+ @saveProgress = ko.observable(0)
257
+ @extend()
258
+ @init()
259
+ @is_ready = ko.dependentObservable ->
260
+ @model_state() == ko.modelStates.READY
261
+ , this
262
+ @is_loading = ko.dependentObservable ->
263
+ @model_state() == ko.modelStates.LOADING
264
+ , this
265
+ @is_saving = ko.dependentObservable ->
266
+ @model_state() == ko.modelStates.SAVING
267
+ , this
268
+ @is_editing = ko.dependentObservable ->
269
+ @model_state() == ko.modelStates.EDITING
270
+ , this
271
+ @is_new = ko.dependentObservable ->
272
+ @id() == ''
273
+ , this
274
+ @is_dirty = ko.dependentObservable ->
275
+ JSON.stringify(@db_state()) != JSON.stringify(@toJS())
276
+ , this
277
+ @has_errors = ko.dependentObservable ->
278
+ @errors().length > 0
279
+ , this
280
+ @handleData(data || {})
281
+ handleData : (resp) ->
282
+ ko.absorbModel(resp, this)
283
+ @db_state(@toJS())
284
+ load : (opts, callback)->
285
+ @adapter.load
286
+ data : opts
287
+ success : (resp)=>
288
+ ret_data = if opts.fields? then ko.copyObject(resp.data, opts.fields) else resp.data
289
+ @handleData(ret_data)
290
+ callback(resp) if callback?
291
+ @model_state(ko.modelStates.LOADING)
292
+ reloadFields : (fields, callback)->
293
+ opts = ko.copyObject(@toJS(), @load_fields)
294
+ opts['fields'] = fields
295
+ @load(opts, callback)
296
+ reload : (callback)->
297
+ opts = ko.copyObject(@toJS(), @load_fields)
298
+ @load(opts, callback)
299
+ save : (fields, callback) ->
300
+ console.log("Saving fields #{fields}")
301
+ if (@model_state() != ko.modelStates.READY)
302
+ console.log("Save postponed.")
303
+ return
304
+ opts = @toJS(fields)
305
+ opts['id'] = @id()
306
+ @adapter.save
307
+ data: opts
308
+ progress : (ev)=>
309
+ @saveProgress( Math.floor( ev.loaded / ev.total * 100 ) )
310
+ success : (resp)=>
311
+ @handleData(resp.data)
312
+ callback(resp) if callback?
313
+ error : =>
314
+ console.log("Save error encountered")
315
+ @model_state(ko.modelStates.READY)
316
+ @model_state(ko.modelStates.SAVING)
317
+ reset : ->
318
+ @model_state(ko.modelStates.LOADING)
319
+ @id('')
320
+ @init()
321
+ @db_state(@toJS())
322
+ @saveProgress(0)
323
+ @model_state(ko.modelStates.READY)
324
+ delete : (fields, callback)=>
325
+ fields ||= ['id']
326
+ if (@model_state() != ko.modelStates.READY)
327
+ console.log("Delete postponed.")
328
+ return
329
+ opts = @toJS(fields)
330
+ opts['id'] = @id()
331
+ @adapter.delete
332
+ data : opts
333
+ success : (resp)=>
334
+ @handleData(resp.data)
335
+ callback(resp) if callback?
336
+ error : =>
337
+ console.log("Delete error encountered")
338
+ @model_state(ko.modelStates.READY)
339
+ @model_state(ko.modelStates.SAVING)
340
+ toJS : (flds)=>
341
+ flds ||= @fields
342
+ obj = {}
343
+ for prop in flds
344
+ if typeof(@[prop].toJS) == 'function'
345
+ obj[prop] = @[prop].toJS()
346
+ else
347
+ obj[prop] = @[prop]()
348
+ obj
349
+ absorb : (model) =>
350
+ @reset()
351
+ @handleData(model.toJS())
352
+
353
+ class @FileModel extends @Model
354
+ extend : ->
355
+ @input = {}
356
+ @input.files = ko.observable([])
357
+ @input.present = ko.computed ->
358
+ @input.files().length > 0
359
+ , this
360
+ @input.file = ko.computed ->
361
+ if @input.present() then @input.files()[0] else null
362
+ , this
363
+ @input.filename = ko.computed ->
364
+ if @input.present() then @input.file().name else ""
365
+ , this
366
+ @input.isImage = ->
367
+ if @input.present() then @input.file().type.match('image.*') else false
368
+ reset : ->
369
+ super
370
+ @input.files([])
371
+ toJS : =>
372
+ @input.file()
373
+
374
+ class @Collection
375
+ init : ->
376
+ constructor: (opts, parent) ->
377
+ @opts = opts || {}
378
+ @events = {}
379
+ @_reqid = 0
380
+ @parent = parent
381
+ @scope = ko.observable(@opts.scope || [])
382
+ @items = ko.observableArray([])
383
+ @views = ko.observableArray([])
384
+ @view_model = ko.observable(@opts.view || View)
385
+ @view_owner = ko.observable(@opts.view_owner || null)
386
+ @page = ko.observable(1)
387
+ @limit = ko.observable(@opts.limit || 4)
388
+ @title = ko.observable(@opts.title || 'Collection')
389
+ @extra_params = ko.observable(@opts.extra_params || {})
390
+ @model = @opts.model
391
+ @adapter = new ModelAdapter()
392
+ @template = ko.observable(@opts.template)
393
+ @model_state = ko.observable(0)
394
+ @is_ready = ko.dependentObservable ->
395
+ @model_state() == ko.modelStates.READY
396
+ , this
397
+ @is_loading = ko.dependentObservable ->
398
+ @model_state() == ko.modelStates.LOADING
399
+ , this
400
+ @is_appending = ko.dependentObservable ->
401
+ @model_state() == ko.modelStates.APPENDING
402
+ , this
403
+ @is_inserting = ko.dependentObservable ->
404
+ @model_state() == ko.modelStates.INSERTING
405
+ , this
406
+ @loadOptions = ko.dependentObservable ->
407
+ opts = @extra_params()
408
+ opts['scope'] = @scope()
409
+ opts['limit'] = @limit()
410
+ opts['page'] = @page()
411
+ opts
412
+ , this
413
+ @scope = ko.intercepter @scope, (obs, prev, curr) ->
414
+ obs(curr)
415
+ console.log("Scope changed from #{prev} to #{curr}")
416
+ #@load()
417
+ , this
418
+ @scopeSelector = ko.observable()
419
+ @scopeSelector.subscribe (val) ->
420
+ opts = @scope()
421
+ opts[@scopeSelector()] = []
422
+ @scope(opts)
423
+ , this
424
+ @hasItems = ko.dependentObservable ->
425
+ @items().length > 0
426
+ , this
427
+ @init()
428
+ setScope : (scp, args) =>
429
+ opts = args
430
+ opts.unshift(scp)
431
+ @scope(opts)
432
+ setView : (view_model, view_owner) =>
433
+ @view_model(view_model)
434
+ @view_owner(view_owner)
435
+ _load : (scope, op, callback)->
436
+ console.log("Loading items for #{scope}")
437
+ op ||= Collection.REPLACE
438
+ reqid = ++@_reqid
439
+ opts = @loadOptions()
440
+ opts.scope = scope
441
+ @adapter.index
442
+ data : opts
443
+ success : (resp)=>
444
+ return if @_reqid != reqid
445
+ @handleData(resp.data, op)
446
+ callback(resp) if callback?
447
+ @events.onchange() if @events.onchange?
448
+ if op == Collection.REPLACE
449
+ @model_state(ko.modelStates.LOADING)
450
+ else if op == Collection.APPEND
451
+ @model_state(ko.modelStates.APPENDING)
452
+ else if op == Collection.INSERT
453
+ @model_state(ko.modelStates.INSERTING)
454
+ load : (scope, callback)->
455
+ @scope(scope) if scope?
456
+ @_load(@scope(), Collection.REPLACE, callback)
457
+ update : (callback)->
458
+ @_load(@scope(), Collection.REPLACE, callback)
459
+ insert : (scope, callback)->
460
+ @_load(scope, Collection.INSERT, callback)
461
+ append : (scope, callback)->
462
+ @_load(scope, Collection.APPEND, callback)
463
+ handleData : (resp, op) =>
464
+ models = []
465
+ views = []
466
+ op ||= Collection.REPLACE
467
+ cls = @view_model()
468
+ if op == Collection.REPLACE
469
+ @items([]); @views([])
470
+ for item, idx in resp
471
+ model = new @model(item, this)
472
+ models.push(model)
473
+ views.push(new cls("view-#{model.id()}", @view_owner(), model))
474
+
475
+ if !op? || op == Collection.REPLACE
476
+ @items(models)
477
+ @views(views)
478
+ console.log("Items loaded")
479
+ else if op == Collection.INSERT
480
+ @items(models.concat(@items()))
481
+ @views(views.concat(@views()))
482
+ else if op == Collection.APPEND
483
+ @items(@items().concat(models))
484
+ @views(@views().concat(views))
485
+ @model_state(ko.modelStates.READY)
486
+ nextPage : ->
487
+ @page(@page() + 1)
488
+ @update()
489
+ prevPage : ->
490
+ @page(@page() - 1)
491
+ @update()
492
+ hasItems : ->
493
+ @items().length > 0
494
+ getItemById : (id)->
495
+ list = @items().filter ((item)=> item.id() == id)
496
+ ret = if list.length > 0 then list[0] else null
497
+ removeDuplicates : ->
498
+ ids = []
499
+ @items().forEach (item, idx, array)->
500
+ if ids.includes(item.id())
501
+ @items.splice(idx, 1)
502
+ @views.splice(idx, 1)
503
+ else
504
+ ids.push(item.id())
505
+ getTemplate : ->
506
+ @template()
507
+ reset : ->
508
+ @page(1)
509
+ @items([])
510
+ @views([])
511
+ toJS : =>
512
+ objs = []
513
+ for item in @items()
514
+ objs.push(item.toJS())
515
+ objs
516
+
517
+ Collection.REPLACE = 0
518
+ Collection.INSERT = 1
519
+ Collection.APPEND = 2
520
+
521
+ class @View
522
+ init : ->
523
+ constructor : (@name, @owner, @model)->
524
+ @app = @owner.app if @owner?
525
+ @views = {}
526
+ @events = {}
527
+ @templateID = "view-#{@name}"
528
+ @fields = []
529
+ @view_name = ko.computed ->
530
+ @templateID
531
+ , this
532
+ @is_visible = ko.observable(false)
533
+ @is_loading = ko.observable(false)
534
+ @errors = ko.observable([])
535
+ @view = null
536
+ @task = ko.observable(null)
537
+ @init()
538
+ show : ->
539
+ @is_visible(true)
540
+ hide : ->
541
+ @events.before_hide() if @events.before_hide?
542
+ @is_visible(false)
543
+ load : ->
544
+ addView : (name, view_class, tpl) ->
545
+ @views[name] = new view_class(name, this)
546
+ @views[name].templateID = tpl
547
+ @["is_task_#{name}"] = ko.computed ->
548
+ @task() == name
549
+ , this
550
+ @["select_task_#{name}"] = =>
551
+ @selectView(name)
552
+ viewList : ->
553
+ list = for name, view of @views
554
+ view
555
+ selectView : (view_name) ->
556
+ args = Array.prototype.slice.call(arguments)
557
+ last_view = @view
558
+ view = @views[view_name]
559
+ if (last_view != view)
560
+ console.log("View [#{view.name}] selected.")
561
+ @view = view
562
+ @task(view.name)
563
+ last_view.hide() if last_view?
564
+ view.show()
565
+ view.load.apply(view, args[1..])
566
+ window.onbeforeunload = @view.events.before_unload
567
+ else
568
+ @view.load.apply(@view, args[1..])
569
+ isTask : (task) ->
570
+ @task() == task
571
+ getViewName : (view) ->
572
+ view.templateID
573
+ showAsOverlay : (tmp, opts, cls)=>
574
+ overlay.add(this, tmp, opts, cls)
575
+ hideOverlay : =>
576
+ overlay.remove(@name)
577
+
578
+ class @ModelAdapter
579
+ constructor : (opts)->
580
+ @save_url = null
581
+ @load_url = null
582
+ @index_url = null
583
+ for prop,val of opts
584
+ @[prop] = val
585
+ load : (opts)->
586
+ $.getJSON @load_url, opts.data, (resp)->
587
+ opts.success(resp)
588
+ index : (opts)->
589
+ $.getJSON (@index_url || @load_url), opts.data, (resp)->
590
+ opts.success(resp)
591
+ save_old : (opts)->
592
+ $.ajax
593
+ type : 'POST'
594
+ url : @save_url
595
+ data : opts.data
596
+ success : opts.success
597
+ error : opts.error
598
+ save : (opts)->
599
+ $.ajax_qs
600
+ type : 'POST'
601
+ url : @save_url
602
+ data : opts.data
603
+ progress : opts.progress
604
+ success : opts.success
605
+ error : opts.error
606
+ send : (opts)->
607
+ $.ajax
608
+ type : 'POST'
609
+ url : opts.url
610
+ data : opts.data
611
+ success : opts.success
612
+ error : opts.error
613
+ delete : (opts)->
614
+ $.ajax
615
+ type : 'DELETE'
616
+ url : @save_url
617
+ data : opts.data
618
+ success : opts.success
619
+ error : opts.error
620
+ add_method : (fn_name, fn)->
621
+ @[fn_name] = fn.bind(this)
622
+
623
+ class @AccountAdapter
624
+ constructor : (opts)->
625
+ @login_url = "/account/login"
626
+ @register_url = "/account/register"
627
+ @enter_code_url = "/account/enter_code"
628
+ @reset_url = "/account/reset"
629
+ @save_url = "/account/save"
630
+ @login_key = "email"
631
+ @password_key = "password"
632
+ for prop,val of opts
633
+ @[prop] = val
634
+ login : (username, password, callback)->
635
+ opts = {}
636
+ opts[@login_key] = username
637
+ opts[@password_key] = password
638
+ $.post @login_url, opts, (resp) =>
639
+ callback(resp)
640
+ register : (opts, callback)->
641
+ $.post @register_url, opts, (resp) =>
642
+ callback(resp)
643
+ sendInviteCode : (code, callback)->
644
+ $.post @enter_code_url, {code : code}, (resp) =>
645
+ callback(resp)
646
+ save : (opts, callback) ->
647
+ $.post @save_url, opts, (resp) =>
648
+ callback(resp)
649
+ resetPassword : (callback)->
650
+ @is_loading(true)
651
+ opts = {}
652
+ opts[@username_key] = @username()
653
+ $.post @reset_url, opts, (resp) =>
654
+ @is_loading(false)
655
+ callback(resp) if callback?
656
+
657
+ class @AppView extends @View
658
+ constructor : (user_model)->
659
+ @app = this
660
+ @path = ko.observable(null)
661
+ @path_parts = []
662
+ @account_model = Model
663
+ super('app', null)
664
+ @current_user = new @account_model()
665
+ @is_logged_in = ko.dependentObservable ->
666
+ !@current_user.is_new()
667
+ , this
668
+ route : (path) ->
669
+ console.log("Loading path '#{path}'")
670
+ @path(path)
671
+ @path_parts = @path().split('/')
672
+ @handlePath(path)
673
+ handlePath : (path) ->
674
+ setUser : (data)->
675
+ @current_user.handleData(data) if data != null
676
+ redirectTo : (path) ->
677
+ $.history.load(path)
678
+
679
+ @initApp = ->
680
+ appViewModel = @appViewModel
681
+ overlay = @overlay
682
+
683
+ appViewModel.setUser(@CURRENT_USER)
684
+
685
+ # navigation
686
+ $.history.init (hash) ->
687
+ if hash == ""
688
+ appViewModel.route('/')
689
+ else
690
+ appViewModel.route(hash)
691
+ , { unescape : ",/" }
692
+
693
+ # layout bindings
694
+ $('body').koBind(appViewModel)
695
+