├── .gitignore ├── .gitmodules ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── css └── main.css ├── iframe ├── css │ └── style.css ├── index.html └── js │ ├── ember.js │ └── jquery-1.6.2.js ├── index.html ├── learn-emberjs.json ├── lib ├── core.js ├── main.js └── sample-code.js ├── notes └── app-creation-notes.txt ├── templates ├── main-page.handlebars └── output-view.handlebars └── webfonts └── webfonts.css /.gitignore: -------------------------------------------------------------------------------- 1 | .rvmrc 2 | bin/ 3 | .bpm 4 | .bundle 5 | assets/ 6 | .DS_Store 7 | dist/ 8 | learn-emberjs.tar.gz 9 | webfonts/MuseoSlab* 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/ember.js"] 2 | path = vendor/ember.js 3 | url = git://github.com/emberjs/ember.js.git 4 | ignore = dirty 5 | [submodule "ace"] 6 | path = ace 7 | url = http://github.com/ajaxorg/ace.git 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gem "bpm", "~>1.0.0" 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | bpm (1.0.0) 5 | eventmachine (~> 1.0.0.beta.4) 6 | execjs (~> 1.2.4) 7 | gemcutter (~> 0.6.1) 8 | highline (~> 1.6.1) 9 | json_pure (~> 1.4.6) 10 | libgems (~> 0.1.3) 11 | sprockets (~> 2.0.0) 12 | thin (~> 1.2) 13 | thor (~> 0.14.3) 14 | daemons (1.1.4) 15 | eventmachine (1.0.0.beta.4) 16 | execjs (1.2.9) 17 | multi_json (~> 1.0) 18 | gemcutter (0.6.1) 19 | highline (1.6.5) 20 | hike (1.2.1) 21 | json_pure (1.4.6) 22 | libgems (0.1.3) 23 | multi_json (1.0.3) 24 | rack (1.3.5) 25 | sprockets (2.0.3) 26 | hike (~> 1.2) 27 | rack (~> 1.0) 28 | tilt (!= 1.3.0, ~> 1.1) 29 | thin (1.2.11) 30 | daemons (>= 1.0.9) 31 | eventmachine (>= 0.12.6) 32 | rack (>= 1.0.0) 33 | thor (0.14.6) 34 | tilt (1.3.3) 35 | 36 | PLATFORMS 37 | ruby 38 | 39 | DEPENDENCIES 40 | bpm (~> 1.0.0) 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Richard Klancer 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Learn.Ember.js 2 | 3 | ### Description: 4 | 5 | A live editor and tutorial system inspired by [learn.knockoutjs.com](http://learn.knockoutjs.com). In progress. 6 | 7 | For fancy fonts, obtain a no-cost license for the web fonts [MuseoSlab 500](http://www.myfonts.com/fonts/exljbris/museo-slab/500/) and [MuseoSlab 500 Italic](http://www.myfonts.com/fonts/exljbris/museo-slab/500-italic/) and put the `.woff`, `.eot`, and `ttf` files into the `webfonts` directory under the names `MuseoSlab-500.*` and `MuseoSlab-500Italic.*`. 8 | 9 | ### Getting started: 10 | 11 | * Make sure you have Ruby 1.9.2 installed. It's recommended to use [rvm](http://rvm.beginrescueend.com/) to manage your Rubies and Gems. 12 | * Clone the repo and cd to the `learn-emberjs` directory: 13 | 14 | ``` 15 | $ git clone http://github.com/rklancer/learn-emberjs.git 16 | $ cd learn-emberjs 17 | ``` 18 | * Install submodules for Ace and Ember.js: 19 | 20 | ``` 21 | $ git submodule update --init 22 | ``` 23 | 24 | * If you're using RVM to manage gemsets, you probably want to create and use a fresh gemset: 25 | 26 | ``` 27 | $ echo 'rvm use 1.9.2@learn-emberjs --create' > .rvmrc 28 | $ cd ..; cd learn-emberjs 29 | ``` 30 | 31 | * Run 'bundle install' to get the BPM gem: 32 | 33 | ``` 34 | $ bundle install 35 | ``` 36 | 37 | * Repeat the rvm and bundle install steps for in `vendor/ember.js`: 38 | 39 | ``` 40 | $ cd vendor/ember.js 41 | $ echo 'rvm use 1.9.2@emberjs --create' > .rvmrc 42 | $ cd ..; cd ember.js 43 | $ bundle install 44 | ``` 45 | 46 | * Run `rake` inside the `vendor/ember.js` folder to build Ember itself: 47 | 48 | ``` 49 | $ rake 50 | ``` 51 | 52 | * Finally, to open a local copy of Learn.Ember.js, run `bpm preview` in the root of the repo, and open http://localhost:4020/index.html in your favorite browser. 53 | 54 | ``` 55 | $ cd ../.. 56 | $ bpm preview 57 | ``` 58 | 59 | ### Building a static version: 60 | 61 | * The rake task `build` will build a static copy of Learn.Ember.js into the directory `dist/`, with the main tutorial page at `dist/index.html`. The cross-iframe access required by Learn.Ember.js will be blocked by Chrome if you open `index.html` using the `file` protocol, so run a local server to visit the tutorial. Assuming you have Python installed on your system, you can run `python -m SimpleHTTPServer` in the `dist/` folder, and then visit http://localhost:8000/index.html in your browser. 62 | 63 | ``` 64 | $ rake build 65 | $ cd dist; python -m SimpleHTTPServer 66 | ``` 67 | 68 | * The (default) rake task `tarball` will build a tarball, `learn-emberjs.tar.gz` which can be inflated into the base directory of your choice on a static webserver. 69 | 70 | ``` 71 | $ rake 72 | $ ls learn.emberjs.tar.gz 73 | ``` 74 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | desc "Puts a static, working copy of the Learn.Ember.js tutorial into ./dist/index.html" 2 | task :build do 3 | sh 'bpm rebuild' 4 | rm_rf 'dist' 5 | mkdir 'dist' 6 | mkdir_p 'dist/ace/build' 7 | cp_r 'ace/build/src', 'dist/ace/build' 8 | cp_r 'assets', 'dist' 9 | # FileUtils.cp_r copies symlinks as symlinks rather than what we want here, which is to copy the the contents 10 | # of the file pointed to by the symlink. This is the behavior of the shell command 'cp -r'. 11 | sh 'cp -r iframe dist' 12 | cp_r 'webfonts', 'dist' 13 | cp 'index.html', 'dist' 14 | end 15 | 16 | desc "Makes a tarball of Learn.Ember.js that can be expanded in the desired directory of an HTTP server" 17 | task :tarball => :build do 18 | sh 'cd dist; tar cfz ../learn-emberjs.tar.gz .' 19 | end 20 | 21 | task :default => :tarball 22 | -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | /* z-indexes. Needed to make sure user can interact with both the text on the left and the fixed-position code editors 2 | on the right. */ 3 | 4 | nav, article { 5 | position: relative; /* needed to make sure that z-index applies */ 6 | z-index: 1; 7 | } 8 | 9 | .ace-editor { 10 | z-index: 2; 11 | } 12 | 13 | /* Overall Layout */ 14 | 15 | body { 16 | margin: 0; 17 | } 18 | 19 | #wrapper { 20 | min-width: 480px; 21 | max-width: 1080px; 22 | margin: 0 auto; 23 | } 24 | 25 | article { 26 | width: 60%; 27 | padding-left: 2em; 28 | padding-right: 1em; 29 | box-sizing: border-box; 30 | -webkit-box-sizing: border-box; 31 | -moz-box-sizing: border-box; 32 | -ms-box-sizing: border-box; 33 | } 34 | 35 | #code-editors { 36 | position: fixed; 37 | left: 0; 38 | right: 0; 39 | top: 76px; 40 | bottom: 0; 41 | } 42 | 43 | #code-editors-wrapper { 44 | position: relative; 45 | top: 0; 46 | bottom: 0; 47 | max-width: 1080px; 48 | width: 100%; 49 | margin: 0 auto; 50 | height: 100%; 51 | } 52 | 53 | .editor-view { 54 | position: absolute; 55 | right: 0; 56 | left: 0; 57 | margin-right: 10px; 58 | margin-left: 60%; 59 | } 60 | 61 | .editor-view > h1 { 62 | width: 100%; 63 | margin: 0; 64 | } 65 | 66 | /* top */ 67 | #js-editor-view { 68 | top: 0; 69 | bottom: 50%; 70 | margin-top: 0; 71 | margin-bottom: 2em; 72 | } 73 | 74 | /* bottom */ 75 | #template-editor-view { 76 | top: 50%; 77 | bottom: 0; 78 | margin-bottom: 2em; 79 | } 80 | 81 | .output-view { 82 | margin: 0.2em 2em 1em 1em; 83 | } 84 | 85 | .output-view > iframe { 86 | height: 160px; 87 | width: 100%; 88 | 89 | transition: height 0.5s; 90 | transition-timing-function: ease-in-out; 91 | -webkit-transition: height 0.5s; 92 | -webkit-transition-timing-function: ease-in-out; 93 | -moz-transition: height 0.5s; 94 | -moz-transition-timing-function: ease-in-out; 95 | -o-transition: height 0.5s; 96 | -o-transition-timing-function: ease-in-out; 97 | } 98 | 99 | .output-view.is-hidden > iframe { 100 | height: 0; 101 | width: 0; 102 | } 103 | 104 | .output-view.is-hidden { 105 | visibility: hidden; 106 | height: 0; 107 | width: 0; 108 | } 109 | 110 | /* Style */ 111 | 112 | body { 113 | font-family: Cambria, "Hoefler Text", Georgia, "Times New Roman", serif; 114 | } 115 | 116 | article { 117 | line-height: 1.5; 118 | color: #444; 119 | } 120 | 121 | p { 122 | font-size: 1.2em; 123 | margin: 0 0 1em 0; 124 | line-spacing: 0.2em; 125 | word-spacing: 0.1em; 126 | } 127 | 128 | pre, code { 129 | font-family: Consolas, Inconsolata, Menlo, Monaco, monospace; 130 | } 131 | 132 | pre { 133 | margin: 0 1em 1em 2em; 134 | font-size: 1em; 135 | } 136 | 137 | p > code { 138 | font-size: .83em; 139 | } 140 | 141 | .editor-view > h1 { 142 | font-family: MuseoSlab-500, Arial, Helvetica, sans-serif; 143 | font-size: 1.2em; 144 | letter-spacing: -1px; 145 | text-align: right; 146 | color: black; 147 | } 148 | 149 | .editor-view > h1:after { 150 | content: " ↓" 151 | } 152 | 153 | h1, h2, h3 { 154 | font-family: MuseoSlab-500, Arial, Helvetica, sans-serif; 155 | font-weight: normal; 156 | font-style: normal; 157 | letter-spacing: -1px; 158 | color: black; 159 | } 160 | 161 | nav > h1 { 162 | font-size: 3em; 163 | text-align: center; 164 | margin: 0.2em 0; 165 | letter-spacing: -2px; 166 | } 167 | 168 | article > h1 { 169 | font-size: 2em; 170 | margin: 0; 171 | } 172 | 173 | article > h2 { 174 | font-size: 1.5em; 175 | margin: 1em 0 0 0; 176 | } 177 | 178 | .ace-editor { 179 | border: 1px solid black; 180 | background-color: white; 181 | } 182 | 183 | .output-view > iframe { 184 | border: 1px solid black; 185 | } 186 | 187 | p .inline-button { 188 | position: relative; 189 | top: -0.2em; 190 | } 191 | 192 | /* effects */ 193 | 194 | .title-contrast { 195 | color: #CF4E1F; 196 | } 197 | 198 | .prototype-notice { 199 | font-style: italic; 200 | background-color: #FFFFCC; 201 | padding: 0.5em; 202 | } 203 | 204 | /* link styles borrowed diveintohtml5.info */ 205 | 206 | a { 207 | text-decoration: none; 208 | } 209 | 210 | a:link { 211 | color: FireBrick; 212 | border-bottom: 1px dotted; 213 | } 214 | 215 | a:visited { 216 | color: RosyBrown; 217 | border-bottom: 1px dotted; 218 | } 219 | 220 | a:link:hover, a:visited:hover { 221 | border-bottom: 1px solid; 222 | } 223 | -------------------------------------------------------------------------------- /iframe/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #f8f8f8; 3 | } -------------------------------------------------------------------------------- /iframe/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /iframe/js/ember.js: -------------------------------------------------------------------------------- 1 | ../../vendor/ember.js/dist/ember.js -------------------------------------------------------------------------------- /iframe/js/jquery-1.6.2.js: -------------------------------------------------------------------------------- 1 | ../../vendor/ember.js/packages/jquery-1.6.2/lib/main.js -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Learn.Ember.js 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /learn-emberjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "learn-emberjs", 3 | "bpm": "1.0.0", 4 | "version": "0.0.1", 5 | "bpm:build": { 6 | "bpm_libs.js": { 7 | "files": [ 8 | "lib", "templates" 9 | ] 10 | }, 11 | "bpm_styles.css": { 12 | "files": [ 13 | "css" 14 | ] 15 | } 16 | }, 17 | "dependencies": { 18 | "spade": ">= 0", 19 | "ember": ">= 0" 20 | } 21 | } -------------------------------------------------------------------------------- /lib/core.js: -------------------------------------------------------------------------------- 1 | /*globals Learn */ 2 | 3 | require('ember'); 4 | 5 | Learn = Ember.Application.create({ 6 | 7 | VERSION: require('learn-emberjs/~package').version, 8 | 9 | // The main app views. These properties are observable, and they are set when the main app view is appended. 10 | 11 | jsEditorView: null, 12 | templateEditorView: null, 13 | lastOutputView: null, 14 | 15 | // These properties are observable, but they are set by an observer on lastOutputView to facilitate access by 16 | // simple dot accessors without needing the get() method (this is useful at the console). 17 | 18 | _document: null, // iframe's document 19 | _window: null, // iframe's window 20 | _Ember: null, // iframe's Ember 21 | _$: null, // iframe's jQuery 22 | _view: null, // The anonymous Ember.View in the iframe that is used to render the template. 23 | 24 | lastOutputViewIframeChanged: function() { 25 | var lastOutputView = this.get('lastOutputView'); 26 | 27 | if (!lastOutputView) return; 28 | 29 | this.set('_window', lastOutputView._window); 30 | this.set('_document', lastOutputView._document); 31 | this.set('_Ember', lastOutputView._Ember); 32 | this.set('_$', lastOutputView._$); 33 | this.set('_view', lastOutputView._view); 34 | 35 | }.observes('.lastOutputView.iframeIsLoaded') 36 | 37 | }); 38 | 39 | 40 | Learn.sampleCodeController = Ember.Object.create({ 41 | 42 | code: '', 43 | template: '', 44 | 45 | load: function() { 46 | var sampleCode = require('./sample-code'); 47 | this.set('code', sampleCode.code); 48 | this.set('template', sampleCode.template); 49 | } 50 | 51 | }); 52 | 53 | 54 | Learn.EditorView = Ember.View.extend({ 55 | 56 | $aceEditor: null, 57 | aceEditor: null, 58 | AceMode: null, 59 | 60 | didInsertElement: function() { 61 | var self = this; 62 | this.initEditor(); 63 | $(window).resize(function() { self.resize(); }); 64 | }, 65 | 66 | initEditor: function() { 67 | var viewId = this.$().attr('id'), 68 | // bless the ac-editor div with a unique id for the benefit of (Learn.)ace.edit 69 | aceId = viewId + '-ace-editor', 70 | self = this; 71 | 72 | this.$aceEditor = this.$('.ace-editor'); 73 | this.$aceEditor.attr('id', aceId); 74 | 75 | this.set('aceEditor', Learn.ace.edit(aceId)); 76 | this.setMode(); 77 | this.setEditorStyle(); 78 | // Turns out you need to do this last, or else the initialization of the *other* ace editor somehow messes up the 79 | // calculation of this.$().width() and this.$().height() (They report the wrong values during initEditor, only 80 | // reporting the correct values after a wall clock delay; this results in our editor being incorrectly sized.) 81 | this.resizeAceEditorDiv(); 82 | 83 | this.get('aceEditor').getSession().on('change', function () { self.editorTextDidChange(); }); 84 | }, 85 | 86 | resizeAceEditorDiv: function() { 87 | this.$aceEditor.css({ height: this.$().height() - 2, width: this.$().width() - 2 }); 88 | }, 89 | 90 | resize: function(evt) { 91 | this.resizeAceEditorDiv(); 92 | this.get('aceEditor').resize(); 93 | }, 94 | 95 | setMode: function(mode) { 96 | var AceMode; 97 | 98 | mode = mode || this.get('MODE'); 99 | AceMode = Learn.aceRequire(mode).Mode; 100 | // NOTE the following line throws exceptions in FF3.6. A workaround is to use a for..in loop to delete properties 101 | // added to Array.prototype by Ember, then call setMode as below, then restore Array.prototype's properties in 102 | // the subsequent line. Another solution might be to run the ace editor in its own iframe. 103 | this.get('aceEditor').getSession().setMode(new AceMode()); 104 | this.set('AceMode', AceMode); 105 | }, 106 | 107 | // Hook for updating editor style as desired. 108 | setEditorStyle: function() { 109 | var editor = this.get('aceEditor'), 110 | session = editor.getSession(); 111 | 112 | editor.setHighlightActiveLine(false); 113 | editor.renderer.setHScrollBarAlwaysVisible(false); 114 | // FIXME: We're just manhandling the vertical scrollbar for now; a better solution would be to implement 115 | // setVScrollBarAlwaysVisible in Ace. 116 | // See: http://groups.google.com/group/ace-discuss/browse_thread/thread/8b191d3324264bb9/786026290fdd8e46 117 | this.$('.ace_sb').hide(); 118 | editor.renderer.setShowPrintMargin(false); 119 | editor.renderer.setShowGutter(false); 120 | session.setUseSoftTabs(true); 121 | session.setTabSize(2); 122 | }, 123 | 124 | // Handle 2-way sync 125 | codeDidChange: function() { 126 | var aceEditor = this.get('aceEditor'), 127 | code = this.get('code'); 128 | 129 | if (aceEditor && code !== this._codeFromEditor) { 130 | this._codeFromUpdate = code; 131 | aceEditor.getSession().setValue(code); 132 | } 133 | }.observes('code', 'aceEditor'), 134 | 135 | editorTextDidChange: function() { 136 | var aceEditor = this.get('aceEditor'), 137 | code; 138 | 139 | if (aceEditor) { 140 | code = aceEditor.getSession().getValue(); 141 | if (code !== this._codeFromUpdate) { 142 | this._codeFromEditor = code; 143 | this.set('code', code); 144 | } 145 | } 146 | } 147 | 148 | }); 149 | 150 | 151 | Learn.JsEditorView = Learn.EditorView.extend({ 152 | MODE: 'ace/mode/javascript', 153 | codeBinding: 'Learn.sampleCodeController.code' 154 | }); 155 | 156 | 157 | Learn.TemplateEditorView = Learn.EditorView.extend({ 158 | MODE: 'ace/mode/html', 159 | codeBinding: 'Learn.sampleCodeController.template' 160 | }); 161 | 162 | 163 | Learn.OutputView = Ember.View.extend({ 164 | templateName: 'learn-emberjs/~templates/output-view', 165 | 166 | codeBinding: 'Learn.sampleCodeController.code', 167 | templateStringBinding: 'Learn.sampleCodeController.template', 168 | 169 | isHidden: true, 170 | iframeIsLoaded: false, 171 | iframeHasRendered: false, 172 | _window: null, 173 | _document: null, 174 | _$: null, 175 | _Ember: null, 176 | _view: null, 177 | 178 | _renderAfterLoad: false, 179 | 180 | didInsertElement: function() { 181 | var self = this; 182 | this.$('.output-iframe').load(function() { self.iframeDidLoad(this); }); 183 | }, 184 | 185 | iframeDidLoad: function(iframeElem) { 186 | var _window = iframeElem.contentWindow; 187 | 188 | this.set('_document', iframeElem.contentDocument); 189 | this.set('_window', _window); 190 | this.set('_$', _window.$); 191 | this.set('_Ember', _window.Ember); 192 | this.set('_view', _window.eval("Ember.View.create().append();")); 193 | 194 | if (this._renderAfterLoad) { 195 | this.renderIframeContents(); 196 | this._renderAfterLoad = false; 197 | } 198 | 199 | this.set('iframeIsLoaded', true); 200 | }, 201 | 202 | reloadIframe: function() { 203 | this._window.location.reload(); 204 | 205 | this.set('_document', null); 206 | this.set('_window', null); 207 | this.set('_$', null); 208 | this.set('_Ember', null); 209 | this.set('_view', null); 210 | 211 | this.set('iframeHasRendered', false); 212 | 213 | // Finally, let the world know these properties have changed. 214 | this.set('iframeIsLoaded', false); 215 | }, 216 | 217 | run: function() { 218 | Learn.set('lastOutputView', this); 219 | 220 | if (this.get('iframeHasRendered')) { 221 | this._renderAfterLoad = true; 222 | this.reloadIframe(); 223 | } 224 | else if (this.get('iframeIsLoaded')) { 225 | this.renderIframeContents(); 226 | } 227 | 228 | this.set('isHidden', false); 229 | }, 230 | 231 | renderIframeContents: function() { 232 | this.appendCode(); 233 | this.renderTemplate(); 234 | this.set('iframeHasRendered', true); 235 | }, 236 | 237 | renderTemplate: function() { 238 | this._view.set('template', this._Ember.Handlebars.compile(this.get('templateString'))); 239 | this._view.rerender(); 240 | }, 241 | 242 | appendCode: function() { 243 | // http://stackoverflow.com/questions/610995/jquery-cant-append-script-element/3603496#3603496 244 | var script = this._document.createElement('script'); 245 | script.type = 'text/javascript'; 246 | script.text = this.get('code'); 247 | this._document.body.appendChild(script); 248 | }, 249 | 250 | rerunButton: Ember.Button.extend({ 251 | disabledBinding: Ember.Binding.not('parentView.iframeHasRendered'), 252 | click: function() { 253 | this.get('parentView').run(); 254 | } 255 | }), 256 | 257 | helpButton: Ember.Button.extend({ 258 | disabled: true 259 | }) 260 | 261 | }); 262 | 263 | 264 | Learn.RunButton = Ember.Button.extend({ 265 | click: function () { 266 | var outputViewId = this.$().parents('p').nextAll('.output-view:first').attr('id'); 267 | if (outputViewId) { 268 | Ember.View.views[outputViewId].run(this); 269 | this.set('disabled', true); 270 | } 271 | } 272 | }); 273 | 274 | 275 | Learn.HelpButton = Ember.Button.extend({ 276 | // TODO 277 | disabled: true 278 | }); 279 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: Learn.Ember.js 3 | // Copyright: ©2011 Richard Klancer 4 | // ========================================================================== 5 | /*globals Learn */ 6 | 7 | require('./core'); 8 | 9 | exports.main = function () { 10 | 11 | Ember.View.create({ 12 | templateName: 'learn-emberjs/~templates/main-page', 13 | 14 | didInsertElement: function() { 15 | Learn.set('jsEditorView', Ember.View.views['js-editor-view']); 16 | Learn.set('templateEditorView', Ember.View.views['template-editor-view']); 17 | } 18 | }).append(); 19 | 20 | Learn.sampleCodeController.load(); 21 | }; 22 | -------------------------------------------------------------------------------- /lib/sample-code.js: -------------------------------------------------------------------------------- 1 | /* Sample code to make the code windows look like they're doing something */ 2 | exports.code = "Names = Ember.Application.create({\n firstName: \"Jim\",\n lastName: \"James\"\n});"; 3 | exports.template = "

First name: TODO

\n

Last name: TODO

"; 4 | -------------------------------------------------------------------------------- /notes/app-creation-notes.txt: -------------------------------------------------------------------------------- 1 | rvm use 1.9.2@learn-sproutcore --create 2 | gem install bpm 3 | cd ~/dev 4 | bpm init learn-sproutcore # after installing bpm into a gemset... 5 | cd learn-sproutcore 6 | bpm add spade 7 | echo "rvm use 1.9.2@learn-sproutcore --create" > .rvmrc 8 | cd ..; cd learn-sproutcore 9 | 10 | bundle install --binstubs 11 | mkdir vendor 12 | git submodule add http://github.com/sproutcore/sproutcore20.git vendor/sproutcore 13 | cd vendor/sproutcore 14 | bundle install 15 | rake 16 | cd ../.. 17 | bin/bpm add sproutcore 18 | 19 | 20 | 21 | 22 | after a few commits, to add Ace: 23 | 24 | git submodule add http://github.com/ajaxorg/ace.git ace 25 | git submodule update --init --recursive 26 | -------------------------------------------------------------------------------- /templates/main-page.handlebars: -------------------------------------------------------------------------------- 1 |
2 | 3 | 6 | 7 |
8 | 9 |

Welcome!

10 | 11 |

This is an early prototype containing just the 12 | first page of a planned interactive tutorial on Ember.js, a JavaScript framework for building 14 | ambitious web applications. For more details about the ongoing development 15 | of this tutorial, see 17 | this blog post. It does not work in Internet Explorer (at least versions 18 | 8 and below).

19 | 20 |

In this tutorial you'll explore the basics of building a web application 21 | using Ember's declarative bindings and MVC-friendly features.

22 | 23 |

You'll learn how to define your UI's appearance using client-side HTML 24 | templates; how to define model, view, and controller objects using 25 | Ember's prototype-friendly class system; and how to use property 26 | accessors and declarative bindings to keep everything neatly 27 | synchronized.

28 | 29 |

Using bindings and view templates

30 | 31 |

In the upper right pane is the Javascript code of a complete, if 32 | simple, Ember application that describes the name of a person. In the 33 | lower right pane is HTML describing how to display the data.

34 | 35 |

Click {{#view Learn.RunButton class="inline-button run-button"}}Run 36 | →{{/view}} to see what this code does.

37 | 38 | {{view Learn.OutputView class="output-view" classBinding="isHidden"}} 39 | 40 |

As you can see, it just lists the first and last names as "TODO". Let's 41 | fix that.

42 | 43 |

The key is that the markup in the lower right isn't just HTML; it's a 44 | Handlebars template which 45 | yields the actual HTML to be rendered. Go ahead and modify the 46 | template, adding Handlebars expressions to display the parts of 47 | the name:

48 | 49 |
<p>First name: {{Names.firstName}}</p>
50 | <p>Last name: {{Names.lastName}}</p>
51 | 52 |

The {{ and }} 53 | “mustaches” begin and end Handlebars expressions, which will 54 | be replaced by whatever value the property paths 55 | Names.firstName and Names.lastName evaluate to. 56 |

57 | 58 |

Now let's see what happens: {{#view Learn.RunButton 59 | class="inline-button run-button"}}Run →{{/view}}.

60 | 61 | {{view Learn.OutputView class="output-view" classBinding="isHidden"}} 62 | 63 |

By the way, if at any point, you find that you've been following the 64 | instructions but can't get the code to work as described, click the 65 | “Help Me Out →” button, and your code will be replaced 66 | with a working copy.

67 | 68 |

Finally, note that the “Run →” button is only needed 69 | here so that we can re-evaluate your code after you change it! As you'll 70 | soon see, Handlebars and Ember work together so that the template's 71 | output — your UI — automatically updates when data changes in 72 | your application.

73 | 74 |
75 | 76 |
77 | 78 |
79 | {{#view Learn.JsEditorView id="js-editor-view" class="editor-view"}} 80 |

Javascript

81 |
82 | {{/view}} 83 | 84 | {{#view Learn.TemplateEditorView id="template-editor-view" class="editor-view"}} 85 |

Handlebars HTML

86 |
87 | {{/view}} 88 |
89 | 90 |
91 | 92 |
-------------------------------------------------------------------------------- /templates/output-view.handlebars: -------------------------------------------------------------------------------- 1 | {{#view rerunButton}}Rerun →{{/view}} 2 | (Code not working? 3 | {{#view helpButton class="inline-button help-button"}}Help Me Out →{{/view}}) 4 | -------------------------------------------------------------------------------- /webfonts/webfonts.css: -------------------------------------------------------------------------------- 1 | /* @license 2 | * MyFonts Webfont Build ID 1554124, 2011-11-19T14:36:20-0500 3 | * 4 | * The fonts listed in this notice are subject to the End User License 5 | * Agreement(s) entered into by the website owner. All other parties are 6 | * explicitly restricted from using the Licensed Webfonts(s). 7 | * 8 | * You may obtain a valid license at the URLs below. 9 | * 10 | * Webfont: Museo Slab 500 by exljbris 11 | * URL: http://www.myfonts.com/fonts/exljbris/museo-slab/500/ 12 | * 13 | * Webfont: Museo Slab 500 Italic by exljbris 14 | * URL: http://www.myfonts.com/fonts/exljbris/museo-slab/500-italic/ 15 | * 16 | * 17 | * License: http://www.myfonts.com/viewlicense?type=web&buildid=1554124 18 | * Licensed pageviews: unlimited 19 | * Webfonts copyright: Copyright (c) 2009 by Jos Buivenga. All rights reserved. 20 | * 21 | * © 2011 Bitstream Inc 22 | */ 23 | @font-face {font-family: 'MuseoSlab-500';src: url('MuseoSlab-500.eot');src: url('MuseoSlab-500.eot?#iefix') format('embedded-opentype'),url('MuseoSlab-500.woff') format('woff'),url('MuseoSlab-500.ttf') format('truetype');} 24 | @font-face {font-family: 'MuseoSlab-500Italic';src: url('MuseoSlab-500Italic.eot');src: url('MuseoSlab-500Italic.eot?#iefix') format('embedded-opentype'),url('MuseoSlab-500Italic.woff') format('woff'),url('MuseoSlab-500Italic.ttf') format('truetype');} 25 | --------------------------------------------------------------------------------