Added UUIDs to timestamps, refactored the build process
* Added UUIDs to `ts_entry` records. Updated `ts_json:construct_record` to
  respond to `uuid` member properties if present. UUIDs are not required by the
  strict parsing functions in `ts_json` because the client will make a request
  with no UUID if it is a purely new timestamp. IN fact, this is the normal
  case. The UUID is only present when another tool is syncing its copy of this
  timeline wand adding entries that it has created and assigned UUIDs to.
* `ts_entry:new` will create a UUID for a new entry if it does not already have
  one.
* Restructured the build process to put all build artifacts into a dedicated
  `build` subdirectory, instead of mising them in an amongst the source code.
* Added the `uuid` module to the project. It can be found at
      https://gitorious.org/avtobiff/erlang-uuid
* Rewrote asset URLs to use relative paths instead of absolute paths. Relative
  paths are correct in this case, becuase assets always live alongside the HTML
  pages. This change was needed to accomodate the new organization of the JDB
  Labs dev environment, where all projects live under subdirectories of the
  same virtual server instead of subdomains.
* Tweaked the timestamp entry fields in the web UI to save when the field is
  blurred, not just when <Enter> or <Ctrl>-<Enter> is pressed (though those
  still work).
			
			
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,4 +1,4 @@ | |||||||
|  | build/ | ||||||
| *.sw? | *.sw? | ||||||
| ebin/ |  | ||||||
| *.beam | *.beam | ||||||
| .sass-cache | .sass-cache | ||||||
|   | |||||||
							
								
								
									
										36
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								Makefile
									
									
									
									
									
								
							| @@ -1,11 +1,11 @@ | |||||||
| MODS = $(wildcard src/*.erl) | MODS = $(wildcard src/*.erl) | ||||||
| BEAMS = $(MODS:src/%.erl=ebin/%.beam) | BEAMS = $(MODS:src/%.erl=build/ebin/%.beam) | ||||||
| TEST_MODS = $(wildcard test/*.erl) | TEST_MODS = $(wildcard test/*.erl) | ||||||
| TEST_BEAMS = $(TEST_MODS:test/%.erl=test/%.beam) | TEST_BEAMS = $(TEST_MODS:test/%.erl=build/test/%.beam) | ||||||
| TS_ROOT=/usr/local/var/yaws/jdb-labs.com/timestamper | TS_ROOT=/usr/local/var/yaws/jdb-labs.com/timestamper | ||||||
| CWD = `pwd` | CWD = `pwd` | ||||||
|  |  | ||||||
| default: compile | default: build | ||||||
|  |  | ||||||
| all : compile test | all : compile test | ||||||
|  |  | ||||||
| @@ -17,10 +17,10 @@ test : start-test-server run-test stop-test-server | |||||||
|  |  | ||||||
| test-shell : compile compile-test config-yaws-dev | test-shell : compile compile-test config-yaws-dev | ||||||
| 	@echo Starting an interactive YAWS shell with test paths loaded. | 	@echo Starting an interactive YAWS shell with test paths loaded. | ||||||
| 	@yaws -i --pa test --id test_inst | 	@yaws -i --pa build/ebin --pa build/test --id test_inst | ||||||
|  |  | ||||||
| run-test : compile compile-test config-yaws-dev | run-test : compile compile-test config-yaws-dev | ||||||
| 	@erl -pa ./ebin -pa ./test -run timestamper_api_tests test -run init stop -noshell | 	@erl -pa ./build/ebin -pa ./build/test -run timestamper_api_tests test -run init stop -noshell | ||||||
|  |  | ||||||
| start-test-server : | start-test-server : | ||||||
| 	@yaws -D --id test_inst | 	@yaws -D --id test_inst | ||||||
| @@ -29,28 +29,30 @@ stop-test-server : | |||||||
| 	@yaws --stop --id test_inst | 	@yaws --stop --id test_inst | ||||||
|  |  | ||||||
| clean: | clean: | ||||||
| 	rm -rf ebin/* erl_crash.dump test/*.beam | 	rm -rf build | ||||||
|  |  | ||||||
| init: | init: | ||||||
| 	-mkdir ebin | 	-mkdir -p build/ebin | ||||||
|  |  | ||||||
| ebin/%.beam : src/%.erl | build/ebin/%.beam : src/%.erl | ||||||
| 	erlc -W -o ebin $< | 	erlc -W -o build/ebin $< | ||||||
|  |  | ||||||
| test/%.beam : test/%.erl | build/test/%.beam : test/%.erl | ||||||
| 	@echo Compiling sources... | 	@echo Compiling sources... | ||||||
| 	erlc -W -o test $< | 	erlc -W -o build/test $< | ||||||
|  |  | ||||||
|  | build: compile | ||||||
|  | 	-mkdir -p build/include | ||||||
|  | 	cp -r www build | ||||||
|  | 	cp lib/* build/ebin | ||||||
|  | 	cp src/ts_db_records.hrl build/include | ||||||
|  | 	cp yaws.prod.conf build/yaws.conf | ||||||
|  |  | ||||||
| deploy: compile | deploy: compile | ||||||
| 	@service yaws stop | 	@service yaws stop | ||||||
| 	@echo Removing existing artifacts. | 	@echo Removing existing artifacts. | ||||||
| 	- @rm -r $(TS_ROOT) | 	- @rm -r $(TS_ROOT) | ||||||
| 	@echo Copying current artifacts. | 	@echo Copying current artifacts. | ||||||
| 	@mkdir -p $(TS_ROOT) | 	@cp -r build $(TS_ROOT) | ||||||
| 	@cp -r www $(TS_ROOT) |  | ||||||
| 	@cp -r ebin $(TS_ROOT) |  | ||||||
| 	@mkdir $(TS_ROOT)/include |  | ||||||
| 	@cp -r src/ts_db_records.hrl $(TS_ROOT)/include/. |  | ||||||
| 	@cp yaws.prod.conf $(TS_ROOT)/yaws.conf |  | ||||||
| 	@service yaws start | 	@service yaws start | ||||||
| 	@echo Done. | 	@echo Done. | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								lib/uuid.app
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								lib/uuid.app
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | {application, uuid, | ||||||
|  |  [{description, "Erlang UUID"}, | ||||||
|  |   {vsn, "0.4.4"}, | ||||||
|  |   {modules, [uuid]}, | ||||||
|  |   {registered, []}, | ||||||
|  |   {applications, [stdlib, crypto]}, | ||||||
|  |   {env, []}]}. | ||||||
| @@ -28,6 +28,8 @@ | |||||||
|  |  | ||||||
| -record(ts_entry, { | -record(ts_entry, { | ||||||
|     ref,        % {username, timelineid, entryid} |     ref,        % {username, timelineid, entryid} | ||||||
|  |     uuid,       % UUID for this entry across all implementations and copies of | ||||||
|  |                 % this timeline. | ||||||
|     timestamp,  % gregorian seconds |     timestamp,  % gregorian seconds | ||||||
|     mark,       % String description of entry |     mark,       % String description of entry | ||||||
|     notes       % String with further notes about the entry |     notes       % String with further notes about the entry | ||||||
|   | |||||||
| @@ -24,7 +24,11 @@ new(ER = #ts_entry{}, ExtData) when is_list(ExtData) -> | |||||||
| do_new(ER = #ts_entry{}) -> | do_new(ER = #ts_entry{}) -> | ||||||
|         {Username, TimelineId, _} = ER#ts_entry.ref, |         {Username, TimelineId, _} = ER#ts_entry.ref, | ||||||
|         NextId = id_counter:next_counter(ts_entry_id), |         NextId = id_counter:next_counter(ts_entry_id), | ||||||
|         NewRow = ER#ts_entry{ref = {Username, TimelineId, NextId}}, |         RowWithRef = ER#ts_entry{ref = {Username, TimelineId, NextId}}, | ||||||
|  |         NewRow = case RowWithRef#ts_entry.uuid of | ||||||
|  |             undefined -> RowWithRef#ts_entry{uuid = uuid:uuid4()}; | ||||||
|  |             _ -> RowWithRef | ||||||
|  |         end, | ||||||
|         ok = mnesia:write(NewRow), |         ok = mnesia:write(NewRow), | ||||||
|         NewRow. |         NewRow. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -46,6 +46,7 @@ record_to_ejson(Record=#ts_timeline{}, ExtData) -> | |||||||
| %  "id":            "1", | %  "id":            "1", | ||||||
| %   "timestamp":    "2011-01-01T14:01.000Z", | %   "timestamp":    "2011-01-01T14:01.000Z", | ||||||
| %   "mark":         "Workout.", | %   "mark":         "Workout.", | ||||||
|  | %   "uuid":         "98ed5198-0c98-47c7-96c9-52b57a2c605a", | ||||||
| %   "notes":        "First workout after a long break."} | %   "notes":        "First workout after a long break."} | ||||||
|  |  | ||||||
| record_to_ejson(Record=#ts_entry{}, ExtData) -> | record_to_ejson(Record=#ts_entry{}, ExtData) -> | ||||||
| @@ -62,6 +63,7 @@ record_to_ejson(Record=#ts_entry{}, ExtData) -> | |||||||
|         {id, EntryId}, |         {id, EntryId}, | ||||||
|         {timestamp, encode_datetime(DateTime)}, |         {timestamp, encode_datetime(DateTime)}, | ||||||
|         {mark, Record#ts_entry.mark}, |         {mark, Record#ts_entry.mark}, | ||||||
|  |         {uuid, uuid:to_string(Record#ts_entry.uuid)}, | ||||||
|         {notes, Record#ts_entry.notes}] ++ |         {notes, Record#ts_entry.notes}] ++ | ||||||
|         lists:map(fun ext_data_to_ejson/1, ExtData)}. |         lists:map(fun ext_data_to_ejson/1, ExtData)}. | ||||||
|  |  | ||||||
| @@ -171,6 +173,7 @@ construct_record(Entry=#ts_entry{}, [{Key, Value}|Fields], ExtData) -> | |||||||
|                 decode_datetime(Value))}, |                 decode_datetime(Value))}, | ||||||
|             Fields, ExtData); |             Fields, ExtData); | ||||||
|         mark -> construct_record(Entry#ts_entry{mark=Value}, Fields, ExtData); |         mark -> construct_record(Entry#ts_entry{mark=Value}, Fields, ExtData); | ||||||
|  |         uuid -> construct_record(Entry#ts_entry{uuid=uuid:to_binary(Value)}, Fields, ExtData); | ||||||
|         notes -> construct_record(Entry#ts_entry{notes=Value}, Fields, ExtData); |         notes -> construct_record(Entry#ts_entry{notes=Value}, Fields, ExtData); | ||||||
|         _Other -> |         _Other -> | ||||||
|             ExtDataProp = ejson_to_ext_data({Key, Value}), |             ExtDataProp = ejson_to_ext_data({Key, Value}), | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
|     <head> |     <head> | ||||||
|         <title>TimeStamper - Simple Time Tracking</title> |         <title>TimeStamper - Simple Time Tracking</title> | ||||||
|         <link href='//fonts.googleapis.com/css?family=Anonymous+Pro|Arvo|Bentham|Cantarell|Josefin+Sans' rel='stylesheet' type='text/css'> |         <link href='//fonts.googleapis.com/css?family=Anonymous+Pro|Arvo|Bentham|Cantarell|Josefin+Sans' rel='stylesheet' type='text/css'> | ||||||
|         <link rel="stylesheet" media="screen" href="/css/ts-screen.css" type="text/css"/> |         <link rel="stylesheet" media="screen" href="css/ts-screen.css" type="text/css"/> | ||||||
|         <!-- Needed for IE, but I'm not sure if I'm going to support IE with this tool. --> |         <!-- Needed for IE, but I'm not sure if I'm going to support IE with this tool. --> | ||||||
|         <!--<script type="text/javascript" src="/js/json2.js"></script>--> |         <!--<script type="text/javascript" src="/js/json2.js"></script>--> | ||||||
|         <!-- PROD --> |         <!-- PROD --> | ||||||
| @@ -12,13 +12,13 @@ | |||||||
|         <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.10/jquery-ui.min.js"></script> |         <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.10/jquery-ui.min.js"></script> | ||||||
|         --> |         --> | ||||||
|         <!-- DEV  --> |         <!-- DEV  --> | ||||||
|         <script type="text/javascript" src="/js/jquery-1.5.js"></script> |         <script type="text/javascript" src="js/jquery-1.5.js"></script> | ||||||
|  |  | ||||||
|         <script type="text/javascript" src="/js/showdown.js"></script> |         <script type="text/javascript" src="js/showdown.js"></script> | ||||||
|         <script type="text/javascript" src="/js/underscore.js"></script> |         <script type="text/javascript" src="js/underscore.js"></script> | ||||||
|         <script type="text/javascript" src="/js/ICanHaz.js"></script> |         <script type="text/javascript" src="js/ICanHaz.js"></script> | ||||||
|         <script type="text/javascript" src="/js/backbone.js"></script> |         <script type="text/javascript" src="js/backbone.js"></script> | ||||||
|         <script type="text/javascript" src="/js/ts.js"></script> |         <script type="text/javascript" src="js/ts.js"></script> | ||||||
|         <script type="text/javascript"> |         <script type="text/javascript"> | ||||||
| <erl> | <erl> | ||||||
| out(YArg) -> | out(YArg) -> | ||||||
| @@ -77,7 +77,7 @@ out(YArg) -> | |||||||
|                     <input class="timeline-id-input" type="text" value='{{id}}'/> |                     <input class="timeline-id-input" type="text" value='{{id}}'/> | ||||||
|                     <ul class="drop-menu-items"> |                     <ul class="drop-menu-items"> | ||||||
|                         <li class="new-timeline-link"><a href="#"> |                         <li class="new-timeline-link"><a href="#"> | ||||||
|                             <img src="/img/round_plus_icon_16.png"/>new</a></li> |                             <img src="img/round_plus_icon_16.png"/>new</a></li> | ||||||
|                     </ul> |                     </ul> | ||||||
|                 </div> |                 </div> | ||||||
|         </script> |         </script> | ||||||
| @@ -86,8 +86,8 @@ out(YArg) -> | |||||||
|         </script> |         </script> | ||||||
|         <script type="text/html" id="entryTemplate"> |         <script type="text/html" id="entryTemplate"> | ||||||
|                     <div class="mark"> |                     <div class="mark"> | ||||||
|                         <img class="expand-entry" src="/img/br_down_icon_16.png"/> |                         <img class="expand-entry" src="img/br_down_icon_16.png"/> | ||||||
|                         <img class="collapse-entry" src="/img/br_up_icon_16.png"/> |                         <img class="collapse-entry" src="img/br_up_icon_16.png"/> | ||||||
|                         <span>{{mark}}</span> |                         <span>{{mark}}</span> | ||||||
|                     </div> |                     </div> | ||||||
|                     <input class="mark-input" type="text" value="{{mark}}"/> |                     <input class="mark-input" type="text" value="{{mark}}"/> | ||||||
|   | |||||||
							
								
								
									
										64
									
								
								www/js/ts.js
									
									
									
									
									
								
							
							
						
						
									
										64
									
								
								www/js/ts.js
									
									
									
									
									
								
							| @@ -130,18 +130,18 @@ $(document).ready(function(){ | |||||||
|             "dblclick div.mark"         : "editMark", |             "dblclick div.mark"         : "editMark", | ||||||
|             "dblclick div.timestamp"    : "editTimestamp", |             "dblclick div.timestamp"    : "editTimestamp", | ||||||
|             "dblclick div.notes"        : "editNotes", |             "dblclick div.notes"        : "editNotes", | ||||||
|             "keypress .mark-input"      : "updateOnEnter", |             "keypress .mark-input"      : "saveOnEnter", | ||||||
|             "keypress .timestamp-input" : "updateOnEnter", |             "keypress .timestamp-input" : "saveOnEnter", | ||||||
|             "keypress .notes-input"     : "updateOnCtrlEnter", |             "keypress .notes-input"     : "saveOnCtrlEnter", | ||||||
|             "blur .mark-input"          : "close", |             "blur .mark-input"          : "save", | ||||||
|             "blur .timestamp-input"     : "close", |             "blur .timestamp-input"     : "save", | ||||||
|             "blur .notes-input"         : "close" |             "blur .notes-input"         : "save" | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|         initialize: function(options) { |         initialize: function(options) { | ||||||
|             _.bindAll(this, 'render', 'close', 'editTImestamp', 'editMark', |             _.bindAll(this, 'render', 'editTImestamp', 'editMark', 'update', | ||||||
|                 'update', 'updateOnEnter', 'updateOnCtrlEnter', 'getViewModel', |                 'saveOnEnter', 'saveOnCtrlEnter', 'getViewModel', | ||||||
|                 'renderNotes', 'showNotes', 'hideNotes'); |                 'renderNotes', 'showNotes', 'hideNotes', 'save'); | ||||||
|  |  | ||||||
|             this.markdownConverter = options.markdownConverter; |             this.markdownConverter = options.markdownConverter; | ||||||
|  |  | ||||||
| @@ -183,8 +183,7 @@ $(document).ready(function(){ | |||||||
|  |  | ||||||
|         renderNotes: function(source) { |         renderNotes: function(source) { | ||||||
|             if (!this.notesCache) { |             if (!this.notesCache) { | ||||||
|                 this.notesCache = this.markdownConverter.makeHtml(source); |                 this.notesCache = this.markdownConverter.makeHtml(source); } | ||||||
|             } |  | ||||||
|  |  | ||||||
|             return this.notesCache |             return this.notesCache | ||||||
|         }, |         }, | ||||||
| @@ -228,26 +227,24 @@ $(document).ready(function(){ | |||||||
|             return data; |             return data; | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|         /** Close editable fields.  */ |         /** Save and close editable fields.  */ | ||||||
|         close: function() { |  | ||||||
|             $(this.el).removeClass('edit-mark edit-timestamp edit-notes'); |  | ||||||
|         }, |  | ||||||
|  |  | ||||||
|         /** Persist changes in input fields.  */ |  | ||||||
|         save: function() { |         save: function() { | ||||||
|             this.model.save({ |             this.model.save({ | ||||||
|                 mark: this.$('.mark-input').val(),  |                 mark: this.$('.mark-input').val(),  | ||||||
|                 timestamp: new Date(this.$('.timestamp-input').val()), |                 timestamp: new Date(this.$('.timestamp-input').val()), | ||||||
|                 notes: this.$('.notes-input').val()}); |                 notes: this.$('.notes-input').val()}); | ||||||
|  |  | ||||||
|  |             this.$('.notes-text').html(this.renderNotes(this.model.get('notes'))); | ||||||
|  |             $(this.el).removeClass('edit-mark edit-timestamp edit-notes'); | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|         /** Event handler for keypresses on entry input fields. */ |         /** Event handler for keypresses on entry input fields. */ | ||||||
|         updateOnEnter: function(e) { |         saveOnEnter: function(e) { | ||||||
|             if(e.keyCode == 13) { this.save(); this.close(); } |             if(e.keyCode == 13) { this.save(); } | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|         updateOnCtrlEnter: function(e) { |         saveOnCtrlEnter: function(e) { | ||||||
|             if (e.keyCode == 10) { this.save(); this.close(); } |             if (e.keyCode == 10) { this.save(); } | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|         /** |         /** | ||||||
| @@ -305,8 +302,7 @@ $(document).ready(function(){ | |||||||
|         showNotes: function() { |         showNotes: function() { | ||||||
|             if (!this.notesCache) { |             if (!this.notesCache) { | ||||||
|                 this.$('.notes-text').html( |                 this.$('.notes-text').html( | ||||||
|                     this.renderNotes(this.model.get('notes'))) |                     this.renderNotes(this.model.get('notes'))); } | ||||||
|             } |  | ||||||
|  |  | ||||||
|             this.$('.notes').slideDown(); |             this.$('.notes').slideDown(); | ||||||
|             $(this.el).addClass('show-notes'); |             $(this.el).addClass('show-notes'); | ||||||
| @@ -473,14 +469,14 @@ $(document).ready(function(){ | |||||||
|         events: { |         events: { | ||||||
|             "dblclick .timeline-id"         : "editId", |             "dblclick .timeline-id"         : "editId", | ||||||
|             "dblclick .timeline-desc"       : "editDesc", |             "dblclick .timeline-desc"       : "editDesc", | ||||||
|             "keypress .timeline-id-input"   : "updateOnEnter", |             "keypress .timeline-id-input"   : "saveOnEnter", | ||||||
|             "keypress .timeline-desc-input" : "updateOnEnter", |             "keypress .timeline-desc-input" : "saveOnEnter", | ||||||
|             "click .new-timeline-link"      : "showNewTimelineDialog" |             "click .new-timeline-link"      : "showNewTimelineDialog" | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|         initialize: function(options) { |         initialize: function(options) { | ||||||
|             _.bindAll(this, 'render', 'renderOne', 'editId', |             _.bindAll(this, 'render', 'renderOne', 'editId', | ||||||
|                 'editDesc', 'updateOnEnter', 'close'); |                 'editDesc', 'saveOnEnter', 'save'); | ||||||
|  |  | ||||||
|             if (options.initialTimelineId == undefined) { |             if (options.initialTimelineId == undefined) { | ||||||
|                 throw "Can not create a TimelineListView without an initial timeline." |                 throw "Can not create a TimelineListView without an initial timeline." | ||||||
| @@ -517,7 +513,7 @@ $(document).ready(function(){ | |||||||
|             return this; |             return this; | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|         close: function() { |         save: function() { | ||||||
|             this.selectedModel.save({ |             this.selectedModel.save({ | ||||||
|                 id: this.$('.timeline-id-input').val(), |                 id: this.$('.timeline-id-input').val(), | ||||||
|                 description: this.$('.timeline-desc-input').val()}); |                 description: this.$('.timeline-desc-input').val()}); | ||||||
| @@ -529,8 +525,8 @@ $(document).ready(function(){ | |||||||
|             TS.app.newTimelineDialog.show(); |             TS.app.newTimelineDialog.show(); | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|         updateOnEnter: function(e) { |         saveOnEnter: function(e) { | ||||||
|             if (e.keyCode == 13) { this.close(); this.save() } |             if (e.keyCode == 13) { this.save(); } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     }); |     }); | ||||||
| @@ -543,11 +539,11 @@ $(document).ready(function(){ | |||||||
|  |  | ||||||
|         events: { |         events: { | ||||||
|             'dblclick .fullname':       'editFullname', |             'dblclick .fullname':       'editFullname', | ||||||
|             'keypress .fullname-input': 'updateOnEnter' |             'keypress .fullname-input': 'saveOnEnter' | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|         initialize: function() { |         initialize: function() { | ||||||
|             _.bindAll(this, 'render', 'close', 'editFullname', 'updateOnEnter'); |             _.bindAll(this, 'render', 'save', 'editFullname', 'saveOnEnter'); | ||||||
|             this.model.bind('change', this.render); |             this.model.bind('change', this.render); | ||||||
|             this.model.view = this; |             this.model.view = this; | ||||||
|         }, |         }, | ||||||
| @@ -563,14 +559,14 @@ $(document).ready(function(){ | |||||||
|             return this; |             return this; | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|         close: function() { |         save: function() { | ||||||
|             this.model.set({name: this.$('fullname-input').val()}); |             this.model.set({name: this.$('fullname-input').val()}); | ||||||
|             this.model.save(); |             this.model.save(); | ||||||
|             $(this.el).removeClass('edit-fullname'); |             $(this.el).removeClass('edit-fullname'); | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|         updateOnEnter: function(e) { |         saveOnEnter: function(e) { | ||||||
|             if (e.keyCode == 13) this.close(); |             if (e.keyCode == 13) this.save(); | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user