├── .gitignore ├── README.md ├── assets ├── app │ ├── grab_answers.js │ ├── javascript.js │ ├── main.js │ ├── show_answers.js │ ├── site.css │ ├── utils.js │ └── wordlist.js ├── bootstrap │ ├── css │ │ ├── bootstrap-responsive.css │ │ ├── bootstrap-responsive.min.css │ │ ├── bootstrap.css │ │ └── bootstrap.min.css │ ├── img │ │ ├── glyphicons-halflings-white.png │ │ └── glyphicons-halflings.png │ └── js │ │ ├── bootstrap.js │ │ └── bootstrap.min.js ├── codemirror │ ├── codemirror.css │ ├── codemirror.js │ └── matchbrackets.js ├── es5shim │ └── es5shim.min.js ├── jquery │ └── jquery-1.10.2.min.js └── rx │ └── rx.all.js ├── index.html └── lessons ├── autocomplete ├── rx │ ├── finish │ │ ├── 1.html │ │ ├── 1.js │ │ ├── 2.html │ │ ├── 2.js │ │ ├── 3.html │ │ ├── 3.js │ │ ├── 4.html │ │ ├── 4.js │ │ ├── 5.html │ │ ├── 5.js │ │ ├── 6.html │ │ └── 6.js │ └── start │ │ ├── 1.html │ │ ├── 1.js │ │ ├── 2.html │ │ ├── 2.js │ │ ├── 3.html │ │ ├── 3.js │ │ ├── 4.html │ │ ├── 4.js │ │ ├── 5.html │ │ ├── 5.js │ │ ├── 6.html │ │ └── 6.js └── without_rx │ ├── finish │ ├── 1.html │ ├── 1.js │ ├── 2.html │ ├── 2.js │ ├── 3.html │ ├── 3.js │ ├── 4.html │ ├── 4.js │ ├── 5.html │ ├── 5.js │ ├── 6.html │ └── 6.js │ └── start │ ├── 1.html │ ├── 1.js │ ├── 2.html │ ├── 2.js │ ├── 3.html │ ├── 3.js │ ├── 4.html │ ├── 4.js │ ├── 5.html │ ├── 5.js │ ├── 6.html │ └── 6.js ├── binding ├── binding.css ├── binding.html └── binding.js ├── schedulers ├── draw.css ├── excanvas_src.js ├── finish.html ├── finish.js ├── requestanimationframe.js ├── start.html └── start.js └── testing ├── tests.html ├── tests.js └── vendor ├── customassertions.js ├── qunit.css └── qunit.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Functional Programming in Javascript 3 | ======= 4 | 5 | This is a series of interactive exercises for learning Microsoft's Reactive Extensions (Rx) Library for Javascript. So why is the title "Functional Programming in Javascript"? Well it turns out that the key to learning Rx is training yourself to use functional programming to manipulate collections. Functional programming provides developers with the tools to abstract common collection operations into reusable, composable building blocks. You'll be surprised to learn that most of the operations you perform on collections can be accomplished with five simple functions: 6 | 7 | 1. map 8 | 2. filter 9 | 3. concatAll 10 | 4. reduce 11 | 5. zip 12 | 13 | Here's my promise to you: if you learn these 5 functions your code will become shorter, more self-descriptive, and more durable. Also, for reasons that might not be obvious right now, you'll learn that these five functions hold the key to simplifying asynchronous programming. Once you've finished this tutorial you'll also have all the tools you need to easily avoid race conditions, propagate and handle asynchronous errors, and sequence events and AJAX requests. In short, *these 5 functions will probably be the most powerful, flexible, and useful functions you'll ever learn.* 14 | 15 | ##[Try it Online](http://reactivex.io/learnrx/). 16 | -------------------------------------------------------------------------------- /assets/app/grab_answers.js: -------------------------------------------------------------------------------- 1 | function setJSONValue(text) { 2 | if (text) { //do it if it is not Empty 3 | try { 4 | JSON.parse(text); 5 | localStorage.setItem('newState', text); 6 | window.location.reload(); 7 | } catch (error) { 8 | alert('Invalid JSON! Please try it again.'); 9 | throw (error); 10 | } 11 | } 12 | } 13 | //check for bootstrap modal 14 | if (typeof $.fn.modal !== 'undefined') { 15 | var isSaveBtn = false, 16 | $getJsonModal = $("#modalGetJSON"), 17 | $showJsonModal = $("#modalShowJSON"), 18 | $saveBtn = $getJsonModal.find('.save-btn'); 19 | var $modalGetJSON = $getJsonModal.modal({show: false}); 20 | var $modalShowJSON = $showJsonModal.modal({show: false}); 21 | $modalGetJSON.on('show.bs.modal', function () { 22 | var modal = $(this); 23 | //do something on show 24 | modal.find('.modal-body [name="answerText"]').val(""); 25 | }) 26 | .on('hide.bs.modal', function (event) { 27 | if (isSaveBtn) { 28 | var modal = $(this); 29 | var answerText = modal.find('.modal-body [name="answerText"]').val(); 30 | setJSONValue(answerText); 31 | } 32 | }); 33 | $saveBtn.click(function () { 34 | isSaveBtn = true; 35 | }); 36 | $modalShowJSON.on('show.bs.modal', function () { 37 | var modal = $(this), 38 | answerBox = modal.find('.modal-body [name="answerText"]'); 39 | //do something on show 40 | var newState = localStorage.getItem("newState"); 41 | if (newState) { 42 | answerBox.val(newState); 43 | } 44 | else { 45 | answerBox.val(''); 46 | } 47 | answerBox.focus(function(){ 48 | setTimeout(function(){ 49 | answerBox.select(); 50 | },200) 51 | }); 52 | }); 53 | $('.set-answer').on('click', function () { 54 | $modalGetJSON.modal('show'); 55 | }); 56 | $('.get-answer').on('click', function () { 57 | $modalShowJSON.modal('show'); 58 | }); 59 | } else { 60 | $('.set-answer').on('click', function () { 61 | var text = ''; 62 | text = window.prompt("Enter your answer JSON below"); 63 | setJSONValue(text); 64 | }); 65 | $('.get-answer').on('click', function () { 66 | window.prompt("Ctrl-C your answer JSON below", localStorage.getItem("newState")); 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /assets/app/javascript.js: -------------------------------------------------------------------------------- 1 | // TODO actually recognize syntax of TypeScript constructs 2 | 3 | CodeMirror.defineMode("javascript", function(config, parserConfig) { 4 | var indentUnit = config.indentUnit; 5 | var jsonMode = parserConfig.json; 6 | var isTS = parserConfig.typescript; 7 | 8 | // Tokenizer 9 | 10 | var keywords = function(){ 11 | function kw(type) {return {type: type, style: "keyword"};} 12 | var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); 13 | var operator = kw("operator"), atom = {type: "atom", style: "atom"}; 14 | 15 | var jsKeywords = { 16 | "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, 17 | "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, 18 | "var": kw("var"), "const": kw("var"), "let": kw("var"), 19 | "function": kw("function"), "catch": kw("catch"), 20 | "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), 21 | "in": operator, "typeof": operator, "instanceof": operator, 22 | "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom 23 | }; 24 | 25 | // Extend the 'normal' keywords with the TypeScript language extensions 26 | if (isTS) { 27 | var type = {type: "variable", style: "variable-3"}; 28 | var tsKeywords = { 29 | // object-like things 30 | "interface": kw("interface"), 31 | "class": kw("class"), 32 | "extends": kw("extends"), 33 | "constructor": kw("constructor"), 34 | 35 | // scope modifiers 36 | "public": kw("public"), 37 | "private": kw("private"), 38 | "protected": kw("protected"), 39 | "static": kw("static"), 40 | 41 | "super": kw("super"), 42 | 43 | // types 44 | "string": type, "number": type, "bool": type, "any": type 45 | }; 46 | 47 | for (var attr in tsKeywords) { 48 | jsKeywords[attr] = tsKeywords[attr]; 49 | } 50 | } 51 | 52 | return jsKeywords; 53 | }(); 54 | 55 | var isOperatorChar = /[+\-*&%=<>!?|]/; 56 | 57 | function chain(stream, state, f) { 58 | state.tokenize = f; 59 | return f(stream, state); 60 | } 61 | 62 | function nextUntilUnescaped(stream, end) { 63 | var escaped = false, next; 64 | while ((next = stream.next()) != null) { 65 | if (next == end && !escaped) 66 | return false; 67 | escaped = !escaped && next == "\\"; 68 | } 69 | return escaped; 70 | } 71 | 72 | // Used as scratch variables to communicate multiple values without 73 | // consing up tons of objects. 74 | var type, content; 75 | function ret(tp, style, cont) { 76 | type = tp; content = cont; 77 | return style; 78 | } 79 | 80 | function jsTokenBase(stream, state) { 81 | var ch = stream.next(); 82 | if (ch == '"' || ch == "'") 83 | return chain(stream, state, jsTokenString(ch)); 84 | else if (/[\[\]{}\(\),;\:\.]/.test(ch)) 85 | return ret(ch); 86 | else if (ch == "0" && stream.eat(/x/i)) { 87 | stream.eatWhile(/[\da-f]/i); 88 | return ret("number", "number"); 89 | } 90 | else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) { 91 | stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/); 92 | return ret("number", "number"); 93 | } 94 | else if (ch == "/") { 95 | if (stream.eat("*")) { 96 | return chain(stream, state, jsTokenComment); 97 | } 98 | else if (stream.eat("/")) { 99 | stream.skipToEnd(); 100 | return ret("comment", "comment"); 101 | } 102 | else if (state.reAllowed) { 103 | nextUntilUnescaped(stream, "/"); 104 | stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla 105 | return ret("regexp", "string-2"); 106 | } 107 | else { 108 | stream.eatWhile(isOperatorChar); 109 | return ret("operator", null, stream.current()); 110 | } 111 | } 112 | else if (ch == "#") { 113 | stream.skipToEnd(); 114 | return ret("error", "error"); 115 | } 116 | else if (isOperatorChar.test(ch)) { 117 | stream.eatWhile(isOperatorChar); 118 | return ret("operator", null, stream.current()); 119 | } 120 | else { 121 | stream.eatWhile(/[\w\$_]/); 122 | var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; 123 | return (known && state.kwAllowed) ? ret(known.type, known.style, word) : 124 | ret("variable", "variable", word); 125 | } 126 | } 127 | 128 | function jsTokenString(quote) { 129 | return function(stream, state) { 130 | if (!nextUntilUnescaped(stream, quote)) 131 | state.tokenize = jsTokenBase; 132 | return ret("string", "string"); 133 | }; 134 | } 135 | 136 | function jsTokenComment(stream, state) { 137 | var maybeEnd = false, ch; 138 | while (ch = stream.next()) { 139 | if (ch == "/" && maybeEnd) { 140 | state.tokenize = jsTokenBase; 141 | break; 142 | } 143 | maybeEnd = (ch == "*"); 144 | } 145 | return ret("comment", "comment"); 146 | } 147 | 148 | // Parser 149 | 150 | var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true}; 151 | 152 | function JSLexical(indented, column, type, align, prev, info) { 153 | this.indented = indented; 154 | this.column = column; 155 | this.type = type; 156 | this.prev = prev; 157 | this.info = info; 158 | if (align != null) this.align = align; 159 | } 160 | 161 | function inScope(state, varname) { 162 | for (var v = state.localVars; v; v = v.next) 163 | if (v.name == varname) return true; 164 | } 165 | 166 | function parseJS(state, style, type, content, stream) { 167 | var cc = state.cc; 168 | // Communicate our context to the combinators. 169 | // (Less wasteful than consing up a hundred closures on every call.) 170 | cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; 171 | 172 | if (!state.lexical.hasOwnProperty("align")) 173 | state.lexical.align = true; 174 | 175 | while(true) { 176 | var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement; 177 | if (combinator(type, content)) { 178 | while(cc.length && cc[cc.length - 1].lex) 179 | cc.pop()(); 180 | if (cx.marked) return cx.marked; 181 | if (type == "variable" && inScope(state, content)) return "variable-2"; 182 | return style; 183 | } 184 | } 185 | } 186 | 187 | // Combinator utils 188 | 189 | var cx = {state: null, column: null, marked: null, cc: null}; 190 | function pass() { 191 | for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); 192 | } 193 | function cont() { 194 | pass.apply(null, arguments); 195 | return true; 196 | } 197 | function register(varname) { 198 | var state = cx.state; 199 | if (state.context) { 200 | cx.marked = "def"; 201 | for (var v = state.localVars; v; v = v.next) 202 | if (v.name == varname) return; 203 | state.localVars = {name: varname, next: state.localVars}; 204 | } 205 | } 206 | 207 | // Combinators 208 | 209 | var defaultVars = {name: "this", next: {name: "arguments"}}; 210 | function pushcontext() { 211 | cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; 212 | cx.state.localVars = defaultVars; 213 | } 214 | function popcontext() { 215 | cx.state.localVars = cx.state.context.vars; 216 | cx.state.context = cx.state.context.prev; 217 | } 218 | function pushlex(type, info) { 219 | var result = function() { 220 | var state = cx.state; 221 | state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info); 222 | }; 223 | result.lex = true; 224 | return result; 225 | } 226 | function poplex() { 227 | var state = cx.state; 228 | if (state.lexical.prev) { 229 | if (state.lexical.type == ")") 230 | state.indented = state.lexical.indented; 231 | state.lexical = state.lexical.prev; 232 | } 233 | } 234 | poplex.lex = true; 235 | 236 | function expect(wanted) { 237 | return function expecting(type) { 238 | if (type == wanted) return cont(); 239 | else if (wanted == ";") return pass(); 240 | else return cont(arguments.callee); 241 | }; 242 | } 243 | 244 | function statement(type) { 245 | if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex); 246 | if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); 247 | if (type == "keyword b") return cont(pushlex("form"), statement, poplex); 248 | if (type == "{") return cont(pushlex("}"), block, poplex); 249 | if (type == ";") return cont(); 250 | if (type == "function") return cont(functiondef); 251 | if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), 252 | poplex, statement, poplex); 253 | if (type == "variable") return cont(pushlex("stat"), maybelabel); 254 | if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), 255 | block, poplex, poplex); 256 | if (type == "case") return cont(expression, expect(":")); 257 | if (type == "default") return cont(expect(":")); 258 | if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), 259 | statement, poplex, popcontext); 260 | return pass(pushlex("stat"), expression, expect(";"), poplex); 261 | } 262 | function expression(type) { 263 | if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator); 264 | if (type == "function") return cont(functiondef); 265 | if (type == "keyword c") return cont(maybeexpression); 266 | if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator); 267 | if (type == "operator") return cont(expression); 268 | if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator); 269 | if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator); 270 | return cont(); 271 | } 272 | function maybeexpression(type) { 273 | if (type.match(/[;\}\)\],]/)) return pass(); 274 | return pass(expression); 275 | } 276 | 277 | function maybeoperator(type, value) { 278 | if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator); 279 | if (type == "operator" && value == "?") return cont(expression, expect(":"), expression); 280 | if (type == ";") return; 281 | if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator); 282 | if (type == ".") return cont(property, maybeoperator); 283 | if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator); 284 | } 285 | function maybelabel(type) { 286 | if (type == ":") return cont(poplex, statement); 287 | return pass(maybeoperator, expect(";"), poplex); 288 | } 289 | function property(type) { 290 | if (type == "variable") {cx.marked = "property"; return cont();} 291 | } 292 | function objprop(type) { 293 | if (type == "variable") cx.marked = "property"; 294 | if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression); 295 | } 296 | function commasep(what, end) { 297 | function proceed(type) { 298 | if (type == ",") return cont(what, proceed); 299 | if (type == end) return cont(); 300 | return cont(expect(end)); 301 | } 302 | return function commaSeparated(type) { 303 | if (type == end) return cont(); 304 | else return pass(what, proceed); 305 | }; 306 | } 307 | function block(type) { 308 | if (type == "}") return cont(); 309 | return pass(statement, block); 310 | } 311 | function maybetype(type) { 312 | if (type == ":") return cont(typedef); 313 | return pass(); 314 | } 315 | function typedef(type) { 316 | if (type == "variable"){cx.marked = "variable-3"; return cont();} 317 | return pass(); 318 | } 319 | function vardef1(type, value) { 320 | if (type == "variable") { 321 | register(value); 322 | return isTS ? cont(maybetype, vardef2) : cont(vardef2); 323 | } 324 | return cont(); 325 | } 326 | function vardef2(type, value) { 327 | if (value == "=") return cont(expression, vardef2); 328 | if (type == ",") return cont(vardef1); 329 | } 330 | function forspec1(type) { 331 | if (type == "var") return cont(vardef1, expect(";"), forspec2); 332 | if (type == ";") return cont(forspec2); 333 | if (type == "variable") return cont(formaybein); 334 | return cont(forspec2); 335 | } 336 | function formaybein(type, value) { 337 | if (value == "in") return cont(expression); 338 | return cont(maybeoperator, forspec2); 339 | } 340 | function forspec2(type, value) { 341 | if (type == ";") return cont(forspec3); 342 | if (value == "in") return cont(expression); 343 | return cont(expression, expect(";"), forspec3); 344 | } 345 | function forspec3(type) { 346 | if (type != ")") cont(expression); 347 | } 348 | function functiondef(type, value) { 349 | if (type == "variable") {register(value); return cont(functiondef);} 350 | if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext); 351 | } 352 | function funarg(type, value) { 353 | if (type == "variable") {register(value); return isTS ? cont(maybetype) : cont();} 354 | } 355 | 356 | // Interface 357 | 358 | return { 359 | startState: function(basecolumn) { 360 | return { 361 | tokenize: jsTokenBase, 362 | reAllowed: true, 363 | kwAllowed: true, 364 | cc: [], 365 | lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), 366 | localVars: parserConfig.localVars, 367 | context: parserConfig.localVars && {vars: parserConfig.localVars}, 368 | indented: 0 369 | }; 370 | }, 371 | 372 | token: function(stream, state) { 373 | if (stream.sol()) { 374 | if (!state.lexical.hasOwnProperty("align")) 375 | state.lexical.align = false; 376 | state.indented = stream.indentation(); 377 | } 378 | if (stream.eatSpace()) return null; 379 | var style = state.tokenize(stream, state); 380 | if (type == "comment") return style; 381 | state.reAllowed = !!(type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/)); 382 | state.kwAllowed = type != '.'; 383 | return parseJS(state, style, type, content, stream); 384 | }, 385 | 386 | indent: function(state, textAfter) { 387 | if (state.tokenize == jsTokenComment) return CodeMirror.Pass; 388 | if (state.tokenize != jsTokenBase) return 0; 389 | var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; 390 | if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; 391 | var type = lexical.type, closing = firstChar == type; 392 | if (type == "vardef") return lexical.indented + 4; 393 | else if (type == "form" && firstChar == "{") return lexical.indented; 394 | else if (type == "stat" || type == "form") return lexical.indented + indentUnit; 395 | else if (lexical.info == "switch" && !closing) 396 | return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); 397 | else if (lexical.align) return lexical.column + (closing ? 0 : 1); 398 | else return lexical.indented + (closing ? 0 : indentUnit); 399 | }, 400 | 401 | electricChars: ":{}" 402 | }; 403 | }); 404 | 405 | CodeMirror.defineMIME("text/javascript", "javascript"); 406 | CodeMirror.defineMIME("application/json", {name: "javascript", json: true}); 407 | CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true }); 408 | CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true }); 409 | -------------------------------------------------------------------------------- /assets/app/main.js: -------------------------------------------------------------------------------- 1 | var codeMirrors = [], 2 | last = new Date(), 3 | state = null; 4 | 5 | 6 | window.onload = function() { 7 | 8 | /** 9 | * Fix indentation of formatted code to not have any indentation 10 | */ 11 | $.each($('pre,textarea'), function(i, item) { 12 | removeIndentation(item); 13 | }); 14 | 15 | 16 | /** 17 | * Initialize lessons from previous session 18 | */ 19 | state = localStorage.getItem("newState"); 20 | if (state) { 21 | state = JSON.parse(state); 22 | var firstUnfinishedQuestion; 23 | 24 | $(".lesson").each(function(cnt,item) { 25 | var go = $(".go", item)[0], 26 | code = $(".code", item)[0], 27 | post = $(".post", item)[0]; 28 | 29 | if (cnt < state.answers.length) { 30 | $(code).val(state.answers[cnt]); 31 | item.style.visibility = "visible"; 32 | if (post !== undefined) { 33 | post.style.visibility = "visible"; 34 | } 35 | } 36 | else if (cnt === state.answers.length) { 37 | item.style.visibility = "visible"; 38 | } 39 | }); 40 | } 41 | 42 | /** 43 | * Initialize lessons 44 | */ 45 | var lessons = $(".lesson"); 46 | 47 | lessons.each(function(cnt, item) { 48 | var code = $('.code', item)[0]; 49 | if(!code) { 50 | return; 51 | } 52 | var go = $(".go", item)[0], 53 | output = $(".output", item)[0], 54 | showAnswer = $(".showAnswer", item)[0], 55 | fullScreen = $(".fullScreen", item)[0], 56 | answer = $(".answer", item).text(), 57 | resetSprite = $(".resetSprite", item)[0], 58 | sprite = $(".sprite", item)[0], 59 | codeMirror = CodeMirror.fromTextArea(code, { 60 | lineNumbers: true, 61 | matchBrackets: true, 62 | mode: "text/typescript", 63 | tabSize: 2, 64 | indentWithTabs: false, 65 | extraKeys: { 66 | "F4": function(cm) { 67 | enterFullScreen(cm); 68 | }, 69 | "Esc": function(cm) { 70 | exitFullScreen(cm); 71 | } 72 | } 73 | }), 74 | post = $(".post", item)[0], 75 | verifierScript = $(".verifier", item).text(), 76 | controls = $(".control", item); 77 | 78 | codeMirrors.push(codeMirror); 79 | go.onclick = function() { 80 | try { 81 | var verifier = eval("(" + verifierScript + ")"); 82 | 83 | try { 84 | codeMirror.save(); 85 | saveAllAnswers(); 86 | verifier($(code).val(), item); 87 | 88 | if (post !== undefined) { 89 | post.style.visibility = "visible"; 90 | } 91 | if (cnt < lessons.length - 1) { 92 | lessons[cnt + 1].style.visibility = "visible"; 93 | } 94 | 95 | } catch (ex) { 96 | alert(ex); 97 | } 98 | } catch (ex) { 99 | alert(ex); 100 | } 101 | }; 102 | 103 | if (showAnswer) { 104 | showAnswer.onclick = function() { 105 | codeMirror.setValue(answer); 106 | }; 107 | } 108 | 109 | if (fullScreen) { 110 | fullScreen.onclick = function(){ 111 | enterFullScreen(codeMirror); 112 | }; 113 | } 114 | 115 | if (resetSprite) { 116 | resetSprite.onclick = function() { 117 | if (sprite) { 118 | sprite.style.top = sprite.style.left = ""; 119 | } 120 | }; 121 | } 122 | 123 | }); 124 | 125 | 126 | function enterFullScreen(codeMirror){ 127 | codeMirror.setOption('fullScreen', true); 128 | var backdrop = $('').appendTo('body'); 129 | backdrop.click(function(){ 130 | exitFullScreen(codeMirror); 131 | }); 132 | } 133 | 134 | function exitFullScreen(codeMirror){ 135 | codeMirror.setOption('fullScreen', false); 136 | $('.editorBackdrop').remove(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /assets/app/show_answers.js: -------------------------------------------------------------------------------- 1 | $('.show-answers').on('click', function(){ 2 | $(".lesson").each(function(cnt,item) { 3 | var go = $(".go", item)[0], 4 | code = $(".code", item)[0], 5 | answer = $(".answer", item).text(), 6 | post = $(".post", item)[0], 7 | codeMirror = window.codeMirrors[cnt]; 8 | 9 | // Prevents the screen from freezing 10 | setTimeout(function(){ 11 | // Copy from code element if no answer is required for a lesson 12 | if (answer === "") answer = $(code).text(); 13 | // Save code only if the lesson has any 14 | if (codeMirror != null) { 15 | codeMirror.setValue(answer); 16 | codeMirror.save(); 17 | } 18 | item.style.visibility = "visible"; 19 | if (post !== undefined) { 20 | post.style.visibility = "visible"; 21 | } 22 | }, 1); 23 | }); 24 | }); -------------------------------------------------------------------------------- /assets/app/site.css: -------------------------------------------------------------------------------- 1 | body {font-family: sans-serif; padding:0 5px; } 2 | textarea, pre { -moz-tab-size: 4; -o-tab-size: 4; -webkit-tab-size: 4; tab-size: 4; } 3 | 4 | pre {background-color: #F3F3F3; padding: 10px 10px 0 10px; font-family: 'courier new'; font-size: 14px;} 5 | .content { width: 100%; max-width: 850px; margin: 0 auto} 6 | .output { background-color: #E6E6E6; border: 1px black solid; } 7 | .code { font-family: monospace } 8 | .model { font-family: monospace; width: 76%; max-width: 500px; height: 400px } 9 | .illegal { color: red; } 10 | .legal {color: green; } 11 | .question { border: solid 1px black; } 12 | .lesson {visibility: hidden; } 13 | .post { visibility: hidden; } 14 | .aside { font-size: smaller; } 15 | .verifier { display: none; } 16 | .answer { display: none; } 17 | .center {text-align: center} 18 | 19 | /* Override CodeMirror */ 20 | .CodeMirror {border: 1px solid #ccc; height: 450px;} 21 | .CodeMirror-scroll {} 22 | .CodeMirror-fullscreen {position: fixed; top: 20px; left: 20px; right: 20px; bottom: 20px; height: auto; z-index: 9;} 23 | 24 | /* Pretty CSS Buttons */ 25 | .blue-button, .showAnswer{background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #63b8ee), color-stop(1, #468ccf) );background:-moz-linear-gradient( center top, #63b8ee 5%, #468ccf 100% );filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#63b8ee', endColorstr='#468ccf');background-color:#63b8ee;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;text-indent:0;border:1px solid #3866a3;display:inline-block;color:#14396a;font-family:Arial;font-size:15px;font-weight:bold;font-style:normal;margin:2px;height:26px;width:133px;text-decoration:none;text-align:center;text-shadow:1px 1px 0px #7cacde;} 26 | .blue-button:hover, .showAnswer:hover{background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #468ccf), color-stop(1, #63b8ee) );background:-moz-linear-gradient( center top, #468ccf 5%, #63b8ee 100% );filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#468ccf', endColorstr='#63b8ee');background-color:#468ccf;} 27 | .blue-button:active, .showAnswer:active{position:relative;top:1px;} 28 | .green-button, .go{background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #77d42a), color-stop(1, #5cb811) );background:-moz-linear-gradient( center top, #77d42a 5%, #5cb811 100% );filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#77d42a', endColorstr='#5cb811');background-color:#77d42a;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;text-indent:0;border:1px solid #268a16;display:inline-block;color:#306108;font-family:Arial;font-size:15px;font-weight:bold;font-style:normal;height:26px;margin:2px;width:61px;text-decoration:none;text-align:center;text-shadow:1px 1px 0px #aade7c;} 29 | .green-button:hover, .go:hover{background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #5cb811), color-stop(1, #77d42a) );background:-moz-linear-gradient( center top, #5cb811 5%, #77d42a 100% );filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5cb811', endColorstr='#77d42a');background-color:#5cb811;} 30 | .green-button:active, .go:active{position:relative;top:1px;} 31 | .white-button, .fullScreen{background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #ffffff), color-stop(1, #aaaaaa) );background:-moz-linear-gradient( center top, #ffffff 5%, #aaaaaa 100% );filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#aaaaaa');background-color:#ffffff;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;text-indent:0;border:1px solid #268a16;display:inline-block;color:#306108;font-family:Arial;font-size:15px;font-weight:bold;font-style:normal;height:26px;margin:2px;width:145px;text-decoration:none;text-align:center;text-shadow:1px 1px 0px #eeeeee;} 32 | .white-button:hover, .fullScreen:hover{background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #aaaaaa), color-stop(1, #ffffff) );background:-moz-linear-gradient( center top, #aaaaaa 5%, #77d42a 100% );filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#aaaaaa', endColorstr='#77d42a');background-color:#aaaaaa;} 33 | .white-button:active, .fullScreen:active{position:relative;top:1px;} 34 | 35 | .set-answer, .get-answer { width: 200px; height:19px; font-size: 13px; } 36 | .show-answers { width:100%; max-width: 450px; height:25px; font-size: 15px; } 37 | .grab-answers { font-size: 12px; padding: 10px; background-color: #ccc; text-align: center; } 38 | 39 | -------------------------------------------------------------------------------- /assets/app/utils.js: -------------------------------------------------------------------------------- 1 | var seq = function(array, interval) { 2 | interval = interval || 500; 3 | return Rx.Observable.create(function(observer) { 4 | var counter = 0, 5 | lastValue, 6 | timeoutSubscription = null, 7 | unsubscribed = false; 8 | 9 | function doWork() { 10 | if (counter === array.length) { 11 | if (lastValue !== undefined) { 12 | observer.complete(); 13 | return; 14 | } 15 | } 16 | 17 | lastValue = array[counter]; 18 | if (lastValue === undefined) { 19 | counter++; 20 | timeoutSubscription = window.setTimeout(doWork, interval); 21 | } 22 | else { 23 | counter++; 24 | observer.next(lastValue); 25 | if (!unsubscribed) { 26 | doWork(); 27 | } 28 | } 29 | } 30 | 31 | doWork(); 32 | 33 | return function() { 34 | unsubscribed = true; 35 | if (timeoutSubscription !== null) { 36 | testing = window.clearTimeout(timeoutSubscription); 37 | } 38 | }; 39 | }); 40 | }; 41 | 42 | var jQueryMock = { 43 | getJSON: function(url, bag) { 44 | if (Math.random() > 0.9) { 45 | if (bag.error) { 46 | bag.error("There was a connectivity error."); 47 | } 48 | } 49 | else if (url.indexOf("abTestInformation") !== -1) { 50 | window.setTimeout( 51 | function() { 52 | bag.success({ urlPrefix: "billboardTest" }); 53 | }, 54 | 500); 55 | } 56 | else if (url.indexOf("config") !== -1) { 57 | window.setTimeout( 58 | function() { 59 | bag.success({ showInstantQueue: true }); 60 | }, 61 | 500); 62 | } 63 | else if (url.indexOf("movieLists") !== -1) { 64 | window.setTimeout( 65 | function() { 66 | bag.success({ 67 | list: [ 68 | { name: "Thrillers", videos: [234324,234322314,23,5,435,12,3,234,34,23324] }, 69 | { name: "New Releases", videos: [234324,234322314,23,5,435,12,3,234,34,23324] } 70 | ] 71 | }); 72 | }, 73 | 500); 74 | } 75 | else if (url.indexOf("queue") !== -1) { 76 | window.setTimeout( 77 | function() { 78 | bag.success({ 79 | list: { name: "Instant Queue", videos: [234324,234322314,23,5,435,12,3,234,34,23324] } 80 | }); 81 | }, 82 | 500); 83 | } 84 | } 85 | }; 86 | 87 | var Observable = Rx.Observable; 88 | Observable.fromEvent = function(dom, name) { 89 | return Observable.create(function(observer) { 90 | var handler = function(e) { 91 | e.preventDefault(); 92 | observer.next(e); 93 | }; 94 | dom.addEventListener(name,handler, false); 95 | 96 | return function() { 97 | dom.removeEventListener(name, handler); 98 | }; 99 | }); 100 | }; 101 | 102 | var oldMerge = Observable.prototype.merge; 103 | 104 | Observable.prototype.mergeAll = function() { 105 | var args = Array.prototype.slice.call(arguments); 106 | if (arguments.length === 0) { 107 | return Observable.prototype.mergeObservable.apply(this, args); 108 | } 109 | else { 110 | return oldMerge.apply(this, args); 111 | } 112 | } 113 | 114 | function deepStringify(obj) { 115 | return JSON.stringify(deepStringifyRecurse(obj)); 116 | } 117 | 118 | function deepStringifyRecurse(obj) { 119 | var ancestors = [obj], 120 | ancestor, 121 | output = {}, 122 | prop, 123 | counter, 124 | value; 125 | 126 | if (obj === null || obj === undefined || typeof obj !== "object") { 127 | return obj; 128 | } 129 | 130 | ancestor = Object.getPrototypeOf(obj); 131 | 132 | while(ancestor !== undefined) { 133 | if (ancestors[ancestors.length-1] === ancestor) { 134 | break; 135 | } 136 | 137 | ancestors.push(ancestor); 138 | ancestor = Object.getPrototypeOf(obj); 139 | } 140 | 141 | for(counter = ancestors.length-1; counter >= 0; counter--) { 142 | ancestor = ancestors[counter]; 143 | for(prop in ancestor) { 144 | output[prop] = deepStringifyRecurse(ancestor[prop]); 145 | } 146 | } 147 | 148 | return output; 149 | } 150 | 151 | Array.prototype.mergeAll = function() { 152 | var results = []; 153 | this.forEach(function(subArray) { 154 | subArray.forEach(function(itemInArray) { 155 | results.push(itemInArray); 156 | }); 157 | }); 158 | 159 | return results; 160 | }; 161 | 162 | Array.prototype.flatMap = function(projection) { 163 | return this. 164 | map(function(item) { 165 | return projection(item); 166 | }). 167 | mergeAll(); 168 | } 169 | 170 | Array.prototype.sortBy = function (keySelector) { 171 | return this.slice().sort(function(a,b) { 172 | var aKey = keySelector(a), 173 | bKey = keySelector(b); 174 | 175 | if (aKey > bKey) { 176 | return 1; 177 | } 178 | else if (bKey > aKey) { 179 | return -1; 180 | } 181 | else { 182 | return 0; 183 | } 184 | }); 185 | }; 186 | 187 | Array.prototype.reduce = function(combiner, initialValue) { 188 | var counter, 189 | accumulatedValue; 190 | 191 | // If the array is empty, do nothing 192 | if (this.length === 0) { 193 | return this; 194 | } 195 | else { 196 | // If the user didn't pass an initial value, use the first item. 197 | if (arguments.length === 1) { 198 | counter = 1; 199 | accumulatedValue = this[0]; 200 | } 201 | else if (arguments.length >= 2) { 202 | counter = 0; 203 | accumulatedValue = initialValue; 204 | } 205 | else { 206 | throw "Invalid arguments."; 207 | } 208 | 209 | // Loop through the array, feeding the current value and the result of 210 | // the previous computation back into the combiner function until 211 | // we've exhausted the entire array and are left with only one function. 212 | while(counter < this.length) { 213 | accumulatedValue = combiner(accumulatedValue, this[counter]) 214 | counter++; 215 | } 216 | 217 | return [accumulatedValue]; 218 | } 219 | }; 220 | 221 | // JSON.stringify(Array.zip([1,2,3],[4,5,6], function(left, right) { return left + right })) === '[5,7,9]' accumulatedValue + currentValue; }); === [6]; 222 | Array.zip = function(left, right, combinerFunction) { 223 | var counter, 224 | results = []; 225 | 226 | for(counter = 0; counter < Math.min(left.length, right.length); counter++) { 227 | results.push(combinerFunction(left[counter], right[counter])); 228 | } 229 | 230 | return results; 231 | }; 232 | 233 | /** 234 | * Show an error message to the user with the given expected and actual results 235 | * @param {string} expected 236 | * @param {string} received 237 | * @param {string} notes 238 | */ 239 | function showLessonErrorMessage(expected, received, notes){ 240 | var errorString = "Expected Output\n" + expected + "\n\nReceived Output\n" + received; 241 | 242 | if (notes) { 243 | errorString += "\n\n" + notes; 244 | } 245 | 246 | throw errorString; 247 | } 248 | 249 | /** 250 | * Remove all indentation tabs used to format the HTML to make the code look nice in the editor 251 | * @param {HTMLElement} element 252 | * @returns {HTMLElement} 253 | */ 254 | function removeIndentation(element) { 255 | if (!element || !element.innerHTML) { 256 | return null; 257 | } 258 | 259 | var tabsRegex = element.innerHTML.match(/(\t*)[^\t]/)[1]; 260 | element.innerHTML = element.innerHTML.replace(new RegExp('(^' + tabsRegex + '|\\n' + tabsRegex + ')', 'g'), '\n').substr(1); 261 | 262 | return element; 263 | } 264 | 265 | 266 | /** 267 | * Save all lesson answers 268 | */ 269 | function saveAllAnswers() { 270 | var answers = []; 271 | 272 | $(".lesson").each(function(cnt,item) { 273 | var go = $(".go", item)[0], 274 | code = $(".code", item)[0]; 275 | 276 | if (window.getComputedStyle(item).visibility !== "hidden") { 277 | answers.push($(code).val()); 278 | } 279 | }); 280 | 281 | localStorage.setItem("newState", JSON.stringify({answers: answers})); 282 | } 283 | 284 | 285 | /** 286 | * Clear out a lesson 287 | * @param num - the lesson number to clear 288 | */ 289 | window.resetLesson = function(num) { 290 | var state = localStorage.getItem("newState"); 291 | state = JSON.parse(state); 292 | state.answers.splice(num - 1,1); 293 | localStorage.setItem("newState", JSON.stringify(state)); 294 | document.location.reload(); 295 | } 296 | 297 | /** 298 | * Convenience method for testing 299 | */ 300 | window.showAllAnswers = function(upTo) { 301 | var lessons = $(".lesson"); 302 | var lessonCount = 1; 303 | lessons.each(function(cnt,item) { 304 | if (upTo && lessonCount++ > (upTo - 1)) return; 305 | 306 | var go = $(".go", item)[0], 307 | code = $(".code", item)[0], 308 | output = $(".output", item)[0], 309 | showAnswer= $(".showAnswer", item)[0], 310 | answer= $(".answer", item).text(), 311 | codeMirror = codeMirrors[cnt], 312 | post = $(".post", item)[0], 313 | verifierScript = $(".verifier", item).text(), 314 | controls = $(".control", item); 315 | 316 | if (answer.length === 0){ 317 | return; 318 | } 319 | 320 | codeMirror.setValue(answer); 321 | 322 | try { 323 | var verifier = eval("(" + verifierScript + ")"); 324 | 325 | try { 326 | codeMirror.save(); 327 | saveAllAnswers(); 328 | verifier($(code).val(), item); 329 | 330 | if (post !== undefined) { 331 | post.style.visibility = "visible"; 332 | } 333 | if (cnt < lessons.length-1) { 334 | lessons[cnt+1].style.visibility = "visible"; 335 | } 336 | 337 | } 338 | catch(ex) { 339 | alert(ex); 340 | } 341 | } 342 | catch(ex) { 343 | alert(ex); 344 | } 345 | }); 346 | }; 347 | 348 | (function($) { 349 | function pasteIntoInput(el, text) { 350 | el.focus(); 351 | if (typeof el.selectionStart == "number") { 352 | var val = el.value; 353 | var selStart = el.selectionStart; 354 | el.value = val.slice(0, selStart) + text + val.slice(el.selectionEnd); 355 | el.selectionEnd = el.selectionStart = selStart + text.length; 356 | } else if (typeof document.selection != "undefined") { 357 | var textRange = document.selection.createRange(); 358 | textRange.text = text; 359 | textRange.collapse(false); 360 | textRange.select(); 361 | } 362 | } 363 | 364 | function allowTabChar(el) { 365 | $(el).keydown(function(e) { 366 | if (e.which == 9) { 367 | pasteIntoInput(this, "\t"); 368 | return false; 369 | } 370 | }); 371 | 372 | // For Opera, which only allows suppression of keypress events, not keydown 373 | $(el).keypress(function(e) { 374 | if (e.which == 9) { 375 | return false; 376 | } 377 | }); 378 | } 379 | 380 | $.fn.allowTabChar = function() { 381 | if (this.jquery) { 382 | this.each(function() { 383 | if (this.nodeType == 1) { 384 | var nodeName = this.nodeName.toLowerCase(); 385 | if (nodeName == "textarea" || (nodeName == "input" && this.type == "text")) { 386 | allowTabChar(this); 387 | } 388 | } 389 | }) 390 | } 391 | return this; 392 | } 393 | })(jQuery); 394 | 395 | -------------------------------------------------------------------------------- /assets/bootstrap/css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.3.2 3 | * 4 | * Copyright 2013 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world by @mdo and @fat. 9 | */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} 10 | -------------------------------------------------------------------------------- /assets/bootstrap/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/learnrx/c361f584e6937fb4174f7a4333e95521c3950bb0/assets/bootstrap/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /assets/bootstrap/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/learnrx/c361f584e6937fb4174f7a4333e95521c3950bb0/assets/bootstrap/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /assets/codemirror/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 300px; 7 | } 8 | .CodeMirror-scroll { 9 | /* Set scrolling behaviour here */ 10 | overflow: auto; 11 | } 12 | 13 | /* PADDING */ 14 | 15 | .CodeMirror-lines { 16 | padding: 4px 0; /* Vertical padding around content */ 17 | } 18 | .CodeMirror pre { 19 | padding: 0 4px; /* Horizontal padding of content */ 20 | } 21 | 22 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 23 | background-color: white; /* The little square between H and V scrollbars */ 24 | } 25 | 26 | /* GUTTER */ 27 | 28 | .CodeMirror-gutters { 29 | border-right: 1px solid #ddd; 30 | background-color: #f7f7f7; 31 | white-space: nowrap; 32 | } 33 | .CodeMirror-linenumbers {} 34 | .CodeMirror-linenumber { 35 | padding: 0 3px 0 5px; 36 | min-width: 20px; 37 | text-align: right; 38 | color: #999; 39 | -moz-box-sizing: content-box; 40 | box-sizing: content-box; 41 | } 42 | 43 | .CodeMirror-guttermarker { color: black; } 44 | .CodeMirror-guttermarker-subtle { color: #999; } 45 | 46 | /* CURSOR */ 47 | 48 | .CodeMirror div.CodeMirror-cursor { 49 | border-left: 1px solid black; 50 | } 51 | /* Shown when moving in bi-directional text */ 52 | .CodeMirror div.CodeMirror-secondarycursor { 53 | border-left: 1px solid silver; 54 | } 55 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { 56 | width: auto; 57 | border: 0; 58 | background: #7e7; 59 | } 60 | .cm-animate-fat-cursor { 61 | width: auto; 62 | border: 0; 63 | -webkit-animation: blink 1.06s steps(1) infinite; 64 | -moz-animation: blink 1.06s steps(1) infinite; 65 | animation: blink 1.06s steps(1) infinite; 66 | } 67 | @-moz-keyframes blink { 68 | 0% { background: #7e7; } 69 | 50% { background: none; } 70 | 100% { background: #7e7; } 71 | } 72 | @-webkit-keyframes blink { 73 | 0% { background: #7e7; } 74 | 50% { background: none; } 75 | 100% { background: #7e7; } 76 | } 77 | @keyframes blink { 78 | 0% { background: #7e7; } 79 | 50% { background: none; } 80 | 100% { background: #7e7; } 81 | } 82 | 83 | /* Can style cursor different in overwrite (non-insert) mode */ 84 | div.CodeMirror-overwrite div.CodeMirror-cursor {} 85 | 86 | .cm-tab { display: inline-block; } 87 | 88 | .CodeMirror-ruler { 89 | border-left: 1px solid #ccc; 90 | position: absolute; 91 | } 92 | 93 | /* DEFAULT THEME */ 94 | 95 | .cm-s-default .cm-keyword {color: #708;} 96 | .cm-s-default .cm-atom {color: #219;} 97 | .cm-s-default .cm-number {color: #164;} 98 | .cm-s-default .cm-def {color: #00f;} 99 | .cm-s-default .cm-variable, 100 | .cm-s-default .cm-punctuation, 101 | .cm-s-default .cm-property, 102 | .cm-s-default .cm-operator {} 103 | .cm-s-default .cm-variable-2 {color: #05a;} 104 | .cm-s-default .cm-variable-3 {color: #085;} 105 | .cm-s-default .cm-comment {color: #a50;} 106 | .cm-s-default .cm-string {color: #a11;} 107 | .cm-s-default .cm-string-2 {color: #f50;} 108 | .cm-s-default .cm-meta {color: #555;} 109 | .cm-s-default .cm-qualifier {color: #555;} 110 | .cm-s-default .cm-builtin {color: #30a;} 111 | .cm-s-default .cm-bracket {color: #997;} 112 | .cm-s-default .cm-tag {color: #170;} 113 | .cm-s-default .cm-attribute {color: #00c;} 114 | .cm-s-default .cm-header {color: blue;} 115 | .cm-s-default .cm-quote {color: #090;} 116 | .cm-s-default .cm-hr {color: #999;} 117 | .cm-s-default .cm-link {color: #00c;} 118 | 119 | .cm-negative {color: #d44;} 120 | .cm-positive {color: #292;} 121 | .cm-header, .cm-strong {font-weight: bold;} 122 | .cm-em {font-style: italic;} 123 | .cm-link {text-decoration: underline;} 124 | 125 | .cm-s-default .cm-error {color: #f00;} 126 | .cm-invalidchar {color: #f00;} 127 | 128 | /* Default styles for common addons */ 129 | 130 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 131 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 132 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 133 | .CodeMirror-activeline-background {background: #e8f2ff;} 134 | 135 | /* STOP */ 136 | 137 | /* The rest of this file contains styles related to the mechanics of 138 | the editor. You probably shouldn't touch them. */ 139 | 140 | .CodeMirror { 141 | line-height: 1; 142 | position: relative; 143 | overflow: hidden; 144 | background: white; 145 | color: black; 146 | } 147 | 148 | .CodeMirror-scroll { 149 | /* 30px is the magic margin used to hide the element's real scrollbars */ 150 | /* See overflow: hidden in .CodeMirror */ 151 | margin-bottom: -30px; margin-right: -30px; 152 | padding-bottom: 30px; 153 | height: 100%; 154 | outline: none; /* Prevent dragging from highlighting the element */ 155 | position: relative; 156 | -moz-box-sizing: content-box; 157 | box-sizing: content-box; 158 | } 159 | .CodeMirror-sizer { 160 | position: relative; 161 | border-right: 30px solid transparent; 162 | -moz-box-sizing: content-box; 163 | box-sizing: content-box; 164 | } 165 | 166 | /* The fake, visible scrollbars. Used to force redraw during scrolling 167 | before actuall scrolling happens, thus preventing shaking and 168 | flickering artifacts. */ 169 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 170 | position: absolute; 171 | z-index: 6; 172 | display: none; 173 | } 174 | .CodeMirror-vscrollbar { 175 | right: 0; top: 0; 176 | overflow-x: hidden; 177 | overflow-y: scroll; 178 | } 179 | .CodeMirror-hscrollbar { 180 | bottom: 0; left: 0; 181 | overflow-y: hidden; 182 | overflow-x: scroll; 183 | } 184 | .CodeMirror-scrollbar-filler { 185 | right: 0; bottom: 0; 186 | } 187 | .CodeMirror-gutter-filler { 188 | left: 0; bottom: 0; 189 | } 190 | 191 | .CodeMirror-gutters { 192 | position: absolute; left: 0; top: 0; 193 | padding-bottom: 30px; 194 | z-index: 3; 195 | } 196 | .CodeMirror-gutter { 197 | white-space: normal; 198 | height: 100%; 199 | -moz-box-sizing: content-box; 200 | box-sizing: content-box; 201 | padding-bottom: 30px; 202 | margin-bottom: -32px; 203 | display: inline-block; 204 | /* Hack to make IE7 behave */ 205 | *zoom:1; 206 | *display:inline; 207 | } 208 | .CodeMirror-gutter-elt { 209 | position: absolute; 210 | cursor: default; 211 | z-index: 4; 212 | } 213 | 214 | .CodeMirror-lines { 215 | cursor: text; 216 | } 217 | .CodeMirror pre { 218 | /* Reset some styles that the rest of the page might have set */ 219 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 220 | border-width: 0; 221 | background: transparent; 222 | font-family: inherit; 223 | font-size: inherit; 224 | margin: 0; 225 | white-space: pre; 226 | word-wrap: normal; 227 | line-height: inherit; 228 | color: inherit; 229 | z-index: 2; 230 | position: relative; 231 | overflow: visible; 232 | } 233 | .CodeMirror-wrap pre { 234 | word-wrap: break-word; 235 | white-space: pre-wrap; 236 | word-break: normal; 237 | } 238 | 239 | .CodeMirror-linebackground { 240 | position: absolute; 241 | left: 0; right: 0; top: 0; bottom: 0; 242 | z-index: 0; 243 | } 244 | 245 | .CodeMirror-linewidget { 246 | position: relative; 247 | z-index: 2; 248 | overflow: auto; 249 | } 250 | 251 | .CodeMirror-widget {} 252 | 253 | .CodeMirror-wrap .CodeMirror-scroll { 254 | overflow-x: hidden; 255 | } 256 | 257 | .CodeMirror-measure { 258 | position: absolute; 259 | width: 100%; 260 | height: 0; 261 | overflow: hidden; 262 | visibility: hidden; 263 | } 264 | .CodeMirror-measure pre { position: static; } 265 | 266 | .CodeMirror div.CodeMirror-cursor { 267 | position: absolute; 268 | border-right: none; 269 | width: 0; 270 | } 271 | 272 | div.CodeMirror-cursors { 273 | visibility: hidden; 274 | position: relative; 275 | z-index: 1; 276 | } 277 | .CodeMirror-focused div.CodeMirror-cursors { 278 | visibility: visible; 279 | } 280 | 281 | .CodeMirror-selected { background: #d9d9d9; } 282 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 283 | .CodeMirror-crosshair { cursor: crosshair; } 284 | 285 | .cm-searching { 286 | background: #ffa; 287 | background: rgba(255, 255, 0, .4); 288 | } 289 | 290 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 291 | .CodeMirror span { *vertical-align: text-bottom; } 292 | 293 | /* Used to force a border model for a node */ 294 | .cm-force-border { padding-right: .1px; } 295 | 296 | @media print { 297 | /* Hide the cursor when printing */ 298 | .CodeMirror div.CodeMirror-cursors { 299 | visibility: hidden; 300 | } 301 | } -------------------------------------------------------------------------------- /assets/codemirror/matchbrackets.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | var ie_lt8 = /MSIE \d/.test(navigator.userAgent) && 13 | (document.documentMode == null || document.documentMode < 8); 14 | 15 | var Pos = CodeMirror.Pos; 16 | 17 | var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"}; 18 | 19 | function findMatchingBracket(cm, where, strict, config) { 20 | var line = cm.getLineHandle(where.line), pos = where.ch - 1; 21 | var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)]; 22 | if (!match) return null; 23 | var dir = match.charAt(1) == ">" ? 1 : -1; 24 | if (strict && (dir > 0) != (pos == where.ch)) return null; 25 | var style = cm.getTokenTypeAt(Pos(where.line, pos + 1)); 26 | 27 | var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config); 28 | if (found == null) return null; 29 | return {from: Pos(where.line, pos), to: found && found.pos, 30 | match: found && found.ch == match.charAt(0), forward: dir > 0}; 31 | } 32 | 33 | // bracketRegex is used to specify which type of bracket to scan 34 | // should be a regexp, e.g. /[[\]]/ 35 | // 36 | // Note: If "where" is on an open bracket, then this bracket is ignored. 37 | // 38 | // Returns false when no bracket was found, null when it reached 39 | // maxScanLines and gave up 40 | function scanForBracket(cm, where, dir, style, config) { 41 | var maxScanLen = (config && config.maxScanLineLength) || 10000; 42 | var maxScanLines = (config && config.maxScanLines) || 1000; 43 | 44 | var stack = []; 45 | var re = config && config.bracketRegex ? config.bracketRegex : /[(){}[\]]/; 46 | var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1) 47 | : Math.max(cm.firstLine() - 1, where.line - maxScanLines); 48 | for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) { 49 | var line = cm.getLine(lineNo); 50 | if (!line) continue; 51 | var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1; 52 | if (line.length > maxScanLen) continue; 53 | if (lineNo == where.line) pos = where.ch - (dir < 0 ? 1 : 0); 54 | for (; pos != end; pos += dir) { 55 | var ch = line.charAt(pos); 56 | if (re.test(ch) && (style === undefined || cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style)) { 57 | var match = matching[ch]; 58 | if ((match.charAt(1) == ">") == (dir > 0)) stack.push(ch); 59 | else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch}; 60 | else stack.pop(); 61 | } 62 | } 63 | } 64 | return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null; 65 | } 66 | 67 | function matchBrackets(cm, autoclear, config) { 68 | // Disable brace matching in long lines, since it'll cause hugely slow updates 69 | var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000; 70 | var marks = [], ranges = cm.listSelections(); 71 | for (var i = 0; i < ranges.length; i++) { 72 | var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, false, config); 73 | if (match && cm.getLine(match.from.line).length <= maxHighlightLen) { 74 | var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; 75 | marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style})); 76 | if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen) 77 | marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style})); 78 | } 79 | } 80 | 81 | if (marks.length) { 82 | // Kludge to work around the IE bug from issue #1193, where text 83 | // input stops going to the textare whever this fires. 84 | if (ie_lt8 && cm.state.focused) cm.focus(); 85 | 86 | var clear = function() { 87 | cm.operation(function() { 88 | for (var i = 0; i < marks.length; i++) marks[i].clear(); 89 | }); 90 | }; 91 | if (autoclear) setTimeout(clear, 800); 92 | else return clear; 93 | } 94 | } 95 | 96 | var currentlyHighlighted = null; 97 | function doMatchBrackets(cm) { 98 | cm.operation(function() { 99 | if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;} 100 | currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets); 101 | }); 102 | } 103 | 104 | CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) { 105 | if (old && old != CodeMirror.Init) 106 | cm.off("cursorActivity", doMatchBrackets); 107 | if (val) { 108 | cm.state.matchBrackets = typeof val == "object" ? val : {}; 109 | cm.on("cursorActivity", doMatchBrackets); 110 | } 111 | }); 112 | 113 | CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);}); 114 | CodeMirror.defineExtension("findMatchingBracket", function(pos, strict, config){ 115 | return findMatchingBracket(this, pos, strict, config); 116 | }); 117 | CodeMirror.defineExtension("scanForBracket", function(pos, dir, style, config){ 118 | return scanForBracket(this, pos, dir, style, config); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /assets/es5shim/es5shim.min.js: -------------------------------------------------------------------------------- 1 | (function(definition){if(typeof define=="function"){define(definition)}else if(typeof YUI=="function"){YUI.add("es5",definition)}else{definition()}})(function(){function Empty(){}if(!Function.prototype.bind){Function.prototype.bind=function bind(that){var target=this;if(typeof target!="function"){throw new TypeError("Function.prototype.bind called on incompatible "+target)}var args=_Array_slice_.call(arguments,1);var bound=function(){if(this instanceof bound){var result=target.apply(this,args.concat(_Array_slice_.call(arguments)));if(Object(result)===result){return result}return this}else{return target.apply(that,args.concat(_Array_slice_.call(arguments)))}};if(target.prototype){Empty.prototype=target.prototype;bound.prototype=new Empty;Empty.prototype=null}return bound}}var call=Function.prototype.call;var prototypeOfArray=Array.prototype;var prototypeOfObject=Object.prototype;var _Array_slice_=prototypeOfArray.slice;var _toString=call.bind(prototypeOfObject.toString);var owns=call.bind(prototypeOfObject.hasOwnProperty);var defineGetter;var defineSetter;var lookupGetter;var lookupSetter;var supportsAccessors;if(supportsAccessors=owns(prototypeOfObject,"__defineGetter__")){defineGetter=call.bind(prototypeOfObject.__defineGetter__);defineSetter=call.bind(prototypeOfObject.__defineSetter__);lookupGetter=call.bind(prototypeOfObject.__lookupGetter__);lookupSetter=call.bind(prototypeOfObject.__lookupSetter__)}if([1,2].splice(0).length!=2){var array_splice=Array.prototype.splice;if(function(){function makeArray(l){var a=[];while(l--){a.unshift(l)}return a}var array=[],lengthBefore;array.splice.bind(array,0,0).apply(null,makeArray(20));array.splice.bind(array,0,0).apply(null,makeArray(26));lengthBefore=array.length;array.splice(5,0,"XXX");if(lengthBefore+1==array.length){return true}}()){Array.prototype.splice=function(start,deleteCount){if(!arguments.length){return[]}else{return array_splice.apply(this,[start===void 0?0:start,deleteCount===void 0?this.length-start:deleteCount].concat(_Array_slice_.call(arguments,2)))}}}else{Array.prototype.splice=function(start,deleteCount){var result,args=_Array_slice_.call(arguments,2),addElementsCount=args.length;if(!arguments.length){return[]}if(start===void 0){start=0}if(deleteCount===void 0){deleteCount=this.length-start}if(addElementsCount>0){if(deleteCount<=0){if(start==this.length){this.push.apply(this,args);return[]}if(start==0){this.unshift.apply(this,args);return[]}}result=_Array_slice_.call(this,start,start+deleteCount);args.push.apply(args,_Array_slice_.call(this,start+deleteCount,this.length));args.unshift.apply(args,_Array_slice_.call(this,0,start));args.unshift(0,this.length);array_splice.apply(this,args);return result}return array_splice.call(this,start,deleteCount)}}}if([].unshift(0)!=1){var array_unshift=Array.prototype.unshift;Array.prototype.unshift=function(){array_unshift.apply(this,arguments);return this.length}}if(!Array.isArray){Array.isArray=function isArray(obj){return _toString(obj)=="[object Array]"}}var boxedString=Object("a"),splitString=boxedString[0]!="a"||!(0 in boxedString);if(!Array.prototype.forEach){Array.prototype.forEach=function forEach(fun){var object=toObject(this),self=splitString&&_toString(this)=="[object String]"?this.split(""):object,thisp=arguments[1],i=-1,length=self.length>>>0;if(_toString(fun)!="[object Function]"){throw new TypeError}while(++i>>0,result=Array(length),thisp=arguments[1];if(_toString(fun)!="[object Function]"){throw new TypeError(fun+" is not a function")}for(var i=0;i>>0,result=[],value,thisp=arguments[1];if(_toString(fun)!="[object Function]"){throw new TypeError(fun+" is not a function")}for(var i=0;i>>0,thisp=arguments[1];if(_toString(fun)!="[object Function]"){throw new TypeError(fun+" is not a function")}for(var i=0;i>>0,thisp=arguments[1];if(_toString(fun)!="[object Function]"){throw new TypeError(fun+" is not a function")}for(var i=0;i>>0;if(_toString(fun)!="[object Function]"){throw new TypeError(fun+" is not a function")}if(!length&&arguments.length==1){throw new TypeError("reduce of empty array with no initial value")}var i=0;var result;if(arguments.length>=2){result=arguments[1]}else{do{if(i in self){result=self[i++];break}if(++i>=length){throw new TypeError("reduce of empty array with no initial value")}}while(true)}for(;i>>0;if(_toString(fun)!="[object Function]"){throw new TypeError(fun+" is not a function")}if(!length&&arguments.length==1){throw new TypeError("reduceRight of empty array with no initial value")}var result,i=length-1;if(arguments.length>=2){result=arguments[1]}else{do{if(i in self){result=self[i--];break}if(--i<0){throw new TypeError("reduceRight of empty array with no initial value")}}while(true)}if(i<0){return result}do{if(i in this){result=fun.call(void 0,result,self[i],i,object)}}while(i--);return result}}if(!Array.prototype.indexOf||[0,1].indexOf(1,2)!=-1){Array.prototype.indexOf=function indexOf(sought){var self=splitString&&_toString(this)=="[object String]"?this.split(""):toObject(this),length=self.length>>>0;if(!length){return-1}var i=0;if(arguments.length>1){i=toInteger(arguments[1])}i=i>=0?i:Math.max(0,length+i);for(;i>>0;if(!length){return-1}var i=length-1;if(arguments.length>1){i=Math.min(i,toInteger(arguments[1]))}i=i>=0?i:length-Math.abs(i);for(;i>=0;i--){if(i in self&&sought===self[i]){return i}}return-1}}if(!Object.keys){var hasDontEnumBug=true,dontEnums=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],dontEnumsLength=dontEnums.length;for(var key in{toString:null}){hasDontEnumBug=false}Object.keys=function keys(object){if(typeof object!="object"&&typeof object!="function"||object===null){throw new TypeError("Object.keys called on a non-object")}var keys=[];for(var name in object){if(owns(object,name)){keys.push(name)}}if(hasDontEnumBug){for(var i=0,ii=dontEnumsLength;i9999?"+":"")+("00000"+Math.abs(year)).slice(0<=year&&year<=9999?-4:-6);length=result.length;while(length--){value=result[length];if(value<10){result[length]="0"+value}}return year+"-"+result.slice(0,2).join("-")+"T"+result.slice(2).join(":")+"."+("000"+this.getUTCMilliseconds()).slice(-3)+"Z"}}var dateToJSONIsSupported=false;try{dateToJSONIsSupported=Date.prototype.toJSON&&new Date(NaN).toJSON()===null&&new Date(negativeDate).toJSON().indexOf(negativeYearString)!==-1&&Date.prototype.toJSON.call({toISOString:function(){return true}})}catch(e){}if(!dateToJSONIsSupported){Date.prototype.toJSON=function toJSON(key){var o=Object(this),tv=toPrimitive(o),toISO;if(typeof tv==="number"&&!isFinite(tv)){return null}toISO=o.toISOString;if(typeof toISO!="function"){throw new TypeError("toISOString property is not callable")}return toISO.call(o)}}if(!Date.parse||"Date.parse is buggy"){Date=function(NativeDate){function Date(Y,M,D,h,m,s,ms){var length=arguments.length;if(this instanceof NativeDate){var date=length==1&&String(Y)===Y?new NativeDate(Date.parse(Y)):length>=7?new NativeDate(Y,M,D,h,m,s,ms):length>=6?new NativeDate(Y,M,D,h,m,s):length>=5?new NativeDate(Y,M,D,h,m):length>=4?new NativeDate(Y,M,D,h):length>=3?new NativeDate(Y,M,D):length>=2?new NativeDate(Y,M):length>=1?new NativeDate(Y):new NativeDate;date.constructor=Date;return date}return NativeDate.apply(this,arguments)}var isoDateExpression=new RegExp("^"+"(\\d{4}|[+-]\\d{6})"+"(?:-(\\d{2})"+"(?:-(\\d{2})"+"(?:"+"T(\\d{2})"+":(\\d{2})"+"(?:"+":(\\d{2})"+"(?:(\\.\\d{1,}))?"+")?"+"("+"Z|"+"(?:"+"([-+])"+"(\\d{2})"+":(\\d{2})"+")"+")?)?)?)?"+"$");var months=[0,31,59,90,120,151,181,212,243,273,304,334,365];function dayFromMonth(year,month){var t=month>1?1:0;return months[month]+Math.floor((year-1969+t)/4)-Math.floor((year-1901+t)/100)+Math.floor((year-1601+t)/400)+365*(year-1970)}for(var key in NativeDate){Date[key]=NativeDate[key]}Date.now=NativeDate.now;Date.UTC=NativeDate.UTC;Date.prototype=NativeDate.prototype;Date.prototype.constructor=Date;Date.parse=function parse(string){var match=isoDateExpression.exec(string);if(match){var year=Number(match[1]),month=Number(match[2]||1)-1,day=Number(match[3]||1)-1,hour=Number(match[4]||0),minute=Number(match[5]||0),second=Number(match[6]||0),millisecond=Math.floor(Number(match[7]||0)*1e3),offset=!match[4]||match[8]?0:Number(new NativeDate(1970,0)),signOffset=match[9]==="-"?1:-1,hourOffset=Number(match[10]||0),minuteOffset=Number(match[11]||0),result;if(hour<(minute>0||second>0||millisecond>0?24:25)&&minute<60&&second<60&&millisecond<1e3&&month>-1&&month<12&&hourOffset<24&&minuteOffset<60&&day>-1&&day=0){c+=data[i];data[i]=Math.floor(c/n);c=c%n*base}}function toString(){var i=size;var s="";while(--i>=0){if(s!==""||i===0||data[i]!==0){var t=String(data[i]);if(s===""){s=t}else{s+="0000000".slice(0,7-t.length)+t}}}return s}function pow(x,n,acc){return n===0?acc:n%2===1?pow(x,n-1,acc*x):pow(x*x,n/2,acc)}function log(x){var n=0;while(x>=4096){n+=12;x/=4096}while(x>=2){n+=1;x/=2}return n}Number.prototype.toFixed=function(fractionDigits){var f,x,s,m,e,z,j,k;f=Number(fractionDigits);f=f!==f?0:Math.floor(f);if(f<0||f>20){throw new RangeError("Number.toFixed called with invalid number of decimals")}x=Number(this);if(x!==x){return"NaN"}if(x<=-1e21||x>=1e21){return String(x)}s="";if(x<0){s="-";x=-x}m="0";if(x>1e-21){e=log(x*pow(2,69,1))-69;z=e<0?x*pow(2,-e,1):x/pow(2,e,1);z*=4503599627370496;e=52-e;if(e>0){multiply(0,z);j=f;while(j>=7){multiply(1e7,0);j-=7}multiply(pow(10,j,1),0);j=e-1;while(j>=23){divide(1<<23);j-=23}divide(1<0){k=m.length;if(k<=f){m=s+"0.0000000000000000000".slice(0,f-k+2)+m}else{m=s+m.slice(0,k-f)+"."+m.slice(k-f)}}else{m=s+m}return m}})()}var string_split=String.prototype.split;if("ab".split(/(?:ab)*/).length!==2||".".split(/(.?)(.?)/).length!==4||"tesst".split(/(s)*/)[1]==="t"||"".split(/.?/).length===0||".".split(/()()/).length>1){(function(){var compliantExecNpcg=/()??/.exec("")[1]===void 0;String.prototype.split=function(separator,limit){var string=this;if(separator===void 0&&limit===0)return[];if(Object.prototype.toString.call(separator)!=="[object RegExp]"){return string_split.apply(this,arguments)}var output=[],flags=(separator.ignoreCase?"i":"")+(separator.multiline?"m":"")+(separator.extended?"x":"")+(separator.sticky?"y":""),lastLastIndex=0,separator=new RegExp(separator.source,flags+"g"),separator2,match,lastIndex,lastLength;string+="";if(!compliantExecNpcg){separator2=new RegExp("^"+separator.source+"$(?!\\s)",flags)}limit=limit===void 0?-1>>>0:limit>>>0;while(match=separator.exec(string)){lastIndex=match.index+match[0].length;if(lastIndex>lastLastIndex){output.push(string.slice(lastLastIndex,match.index));if(!compliantExecNpcg&&match.length>1){match[0].replace(separator2,function(){for(var i=1;i1&&match.index=limit){break}}if(separator.lastIndex===match.index){separator.lastIndex++}}if(lastLastIndex===string.length){if(lastLength||!separator.test("")){output.push("")}}else{output.push(string.slice(lastLastIndex))}return output.length>limit?output.slice(0,limit):output}})()}else if("0".split(void 0,0).length){String.prototype.split=function(separator,limit){if(separator===void 0&&limit===0)return[];return string_split.apply(this,arguments)}}if("".substr&&"0b".substr(-1)!=="b"){var string_substr=String.prototype.substr;String.prototype.substr=function(start,length){return string_substr.call(this,start<0?(start=this.length+start)<0?0:start:start,length)}}var ws=" \n \f\r \xa0\u1680\u180e\u2000\u2001\u2002\u2003"+"\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028"+"\u2029\ufeff";if(!String.prototype.trim||ws.trim()){ws="["+ws+"]";var trimBeginRegexp=new RegExp("^"+ws+ws+"*"),trimEndRegexp=new RegExp(ws+ws+"*$");String.prototype.trim=function trim(){if(this===void 0||this===null){throw new TypeError("can't convert "+this+" to object")}return String(this).replace(trimBeginRegexp,"").replace(trimEndRegexp,"")}}function toInteger(n){n=+n;if(n!==n){n=0}else if(n!==0&&n!==1/0&&n!==-(1/0)){n=(n>0||-1)*Math.floor(Math.abs(n))}return n}function isPrimitive(input){var type=typeof input;return input===null||type==="undefined"||type==="boolean"||type==="number"||type==="string"}function toPrimitive(input){var val,valueOf,toString;if(isPrimitive(input)){return input}valueOf=input.valueOf;if(typeof valueOf==="function"){val=valueOf.call(input);if(isPrimitive(val)){return val}}toString=input.toString;if(typeof toString==="function"){val=toString.call(input);if(isPrimitive(val)){return val}}throw new TypeError}var toObject=function(o){if(o==null){throw new TypeError("can't convert "+o+" to object")}return Object(o)}}); -------------------------------------------------------------------------------- /lessons/autocomplete/rx/finish/1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 1 9 | 10 | 11 | 12 | 13 | 14 |
15 | 19 |
20 |
21 |
22 | 23 | 24 |
25 |
26 |
27 |
28 |
    29 |
    30 |
    31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/finish/1.js: -------------------------------------------------------------------------------- 1 | (function (window, $, undefined) { 2 | 3 | function main () { 4 | var $input = $('#textInput'), 5 | $results = $('#results'); 6 | 7 | $input.keyup(function () { 8 | var text = $(this).val(); 9 | 10 | $results.empty(); 11 | 12 | $('
  • ' + text + '
  • ').appendTo($results); 13 | }); 14 | } 15 | 16 | $(function () { 17 | 18 | main(); 19 | 20 | }); 21 | 22 | }(this, jQuery)) 23 | 24 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/finish/2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 2 9 | 10 | 11 | 12 | 13 | 14 |
    15 | 19 |
    20 |
    21 |
    22 | 23 | 24 |
    25 |
    26 |
    27 |
    28 |
      29 |
      30 |
      31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/finish/2.js: -------------------------------------------------------------------------------- 1 | (function (window, $, undefined) { 2 | 3 | function searchWikipedia (term) { 4 | return $.ajax({ 5 | url: 'http://en.wikipedia.org/w/api.php', 6 | dataType: 'jsonp', 7 | data: { 8 | action: 'opensearch', 9 | format: 'json', 10 | search: encodeURI(term) 11 | } 12 | }); 13 | } 14 | 15 | function main () { 16 | var $input = $('#textInput'), 17 | $results = $('#results'); 18 | 19 | $input.keyup(function () { 20 | var text = $(this).val(); 21 | 22 | $results.empty(); 23 | 24 | searchWikipedia(text).then( 25 | function (data) { 26 | 27 | var result = data[1]; 28 | $.each(result, function (i, value) { 29 | $('
    • ' + value + '
    • ').appendTo($results); 30 | }); 31 | 32 | }, 33 | function (err) { 34 | $('
    • ' + err + '
    • ').appendTo($results); 35 | } 36 | ); 37 | }); 38 | } 39 | 40 | $(function () { 41 | 42 | main(); 43 | 44 | }); 45 | 46 | }(this, jQuery)) 47 | 48 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/finish/3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 3 9 | 10 | 11 | 12 | 13 | 14 |
      15 | 19 |
      20 |
      21 |
      22 | 23 | 24 |
      25 |
      26 |
      27 |
      28 |
        29 |
        30 |
        31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/finish/3.js: -------------------------------------------------------------------------------- 1 | (function (window, $, undefined) { 2 | 3 | function searchWikipedia (term) { 4 | return $.ajax({ 5 | url: 'http://en.wikipedia.org/w/api.php', 6 | dataType: 'jsonp', 7 | data: { 8 | action: 'opensearch', 9 | format: 'json', 10 | search: term 11 | } 12 | }); 13 | } 14 | 15 | function main () { 16 | var $input = $('#textInput'), 17 | $results = $('#results'); 18 | 19 | // Keep track of old state 20 | var jqXHR; 21 | 22 | $input.keyup(function () { 23 | var text = $(this).val(); 24 | 25 | $results.empty(); 26 | 27 | // Cancel previous if one already out there 28 | if (jqXHR && jqXHR.state() === 'pending') { 29 | jqXHR.abort(); 30 | } 31 | 32 | jqXHR = searchWikipedia(text); 33 | 34 | jqXHR.then( 35 | function (data) { 36 | 37 | var result = data[1]; 38 | $.each(result, function (i, value) { 39 | $('
      • ' + value + '
      • ').appendTo($results); 40 | }); 41 | 42 | }, 43 | function (xhr) { 44 | if (xhr.statusText !== 'abort') { 45 | $('
      • ' + xhr.statusText + '
      • ').appendTo($results); 46 | } 47 | 48 | 49 | } 50 | ); 51 | }); 52 | } 53 | 54 | $(function () { 55 | 56 | main(); 57 | 58 | }); 59 | 60 | }(this, jQuery)) 61 | 62 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/finish/4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 4 9 | 10 | 11 | 12 | 13 | 14 |
        15 | 19 |
        20 |
        21 |
        22 | 23 | 24 |
        25 |
        26 |
        27 |
        28 |
          29 |
          30 |
          31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/finish/4.js: -------------------------------------------------------------------------------- 1 | // Add distinct 2 | 3 | (function (window, $, undefined) { 4 | 5 | function searchWikipedia (term) { 6 | return $.ajax({ 7 | url: 'http://en.wikipedia.org/w/api.php', 8 | dataType: 'jsonp', 9 | data: { 10 | action: 'opensearch', 11 | format: 'json', 12 | search: encodeURI(term) 13 | } 14 | }); 15 | } 16 | 17 | function main () { 18 | var $input = $('#textInput'), 19 | $results = $('#results'); 20 | 21 | // Keep track of old state 22 | var jqXHR, 23 | currentText = ''; 24 | 25 | $input.keyup(function () { 26 | var text = $(this).val(); 27 | 28 | // Cancel previous if one already out there 29 | if (jqXHR && jqXHR.state() === 'pending') { 30 | jqXHR.abort(); 31 | } 32 | 33 | // Now check if text has changed 34 | if (text !== currentText) { 35 | currentText = text; 36 | 37 | $results.empty(); 38 | 39 | jqXHR = searchWikipedia(currentText); 40 | 41 | jqXHR.then( 42 | function (data) { 43 | 44 | var result = data[1]; 45 | $.each(result, function (i, value) { 46 | $('
        • ' + value + '
        • ').appendTo($results); 47 | }); 48 | 49 | }, 50 | function (xhr) { 51 | if (xhr.statusText !== 'abort') { 52 | $('
        • ' + xhr.statusText + '
        • ').appendTo($results); 53 | } 54 | } 55 | ); 56 | } 57 | 58 | 59 | }); 60 | } 61 | 62 | $(function () { 63 | 64 | main(); 65 | 66 | }); 67 | 68 | }(this, jQuery)) 69 | 70 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/finish/5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 5 9 | 10 | 11 | 12 | 13 | 14 |
          15 | 19 |
          20 |
          21 |
          22 | 23 | 24 |
          25 |
          26 |
          27 |
          28 |
            29 |
            30 |
            31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/finish/5.js: -------------------------------------------------------------------------------- 1 | // Add debounce/throttle 2 | 3 | (function (window, $, undefined) { 4 | 5 | function searchWikipedia (term) { 6 | return $.ajax({ 7 | url: 'http://en.wikipedia.org/w/api.php', 8 | dataType: 'jsonp', 9 | data: { 10 | action: 'opensearch', 11 | format: 'json', 12 | search: term 13 | } 14 | }); 15 | } 16 | 17 | function debounce(fn, wait) { 18 | var id; 19 | return function () { 20 | var args = arguments, context = this; 21 | 22 | // Cancel already in flight 23 | if (id) { 24 | window.clearTimeout(id); 25 | } 26 | 27 | window.setTimeout(function () { 28 | fn.apply(context, args); 29 | }, wait); 30 | } 31 | } 32 | 33 | function main () { 34 | var $input = $('#textInput'), 35 | $results = $('#results'); 36 | 37 | // Keep track of old state 38 | var jqXHR, 39 | currentText = ''; 40 | 41 | var handler = function () { 42 | var text = $(this).val(); 43 | 44 | // Cancel previous if one already out there 45 | if (jqXHR && jqXHR.state() === 'pending') { 46 | jqXHR.abort(); 47 | } 48 | 49 | // Now check if text has changed 50 | if (text !== currentText) { 51 | currentText = text; 52 | 53 | $results.empty(); 54 | 55 | jqXHR = searchWikipedia(currentText); 56 | 57 | jqXHR.then( 58 | function (data) { 59 | 60 | var result = data[1]; 61 | $.each(result, function (i, value) { 62 | $('
          • ' + value + '
          • ').appendTo($results); 63 | }); 64 | 65 | }, 66 | function (xhr) { 67 | if (xhr.statusText !== 'abort') { 68 | $('
          • ' + xhr.statusText + '
          • ').appendTo($results); 69 | } 70 | } 71 | ); 72 | } 73 | }; 74 | 75 | $input.keyup(debounce(handler, 500)); 76 | } 77 | 78 | $(function () { 79 | 80 | main(); 81 | 82 | }); 83 | 84 | }(this, jQuery)) 85 | 86 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/finish/6.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 6 9 | 10 | 11 | 12 | 13 | 14 |
            15 | 19 |
            20 |
            21 |
            22 | 23 | 24 |
            25 |
            26 |
            27 |
            28 |
              29 |
              30 |
              31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/finish/6.js: -------------------------------------------------------------------------------- 1 | // Add debounce/throttle 2 | 3 | (function (window, $, undefined) { 4 | 5 | function searchWikipedia (term, numTimes) { 6 | var deferred = $.Deferred(); 7 | 8 | (function makeRequest(num) { 9 | $.ajax({ 10 | url: 'http://en.wikipedia.org/w/api.php', 11 | dataType: 'jsonp', 12 | data: { 13 | action: 'opensearch', 14 | format: 'json', 15 | search: term 16 | }, 17 | success: function (data, status, xhr) { 18 | deferred.abort = xhr.abort; 19 | deferred.resolve(data, status, xhr); 20 | }, 21 | error: function (xhr) { 22 | if (xhr.statusText !== 'abort') { 23 | if (num > 0) { 24 | return makeRequest(num - 1); 25 | } else { 26 | deferred.rejectWith(this, arguments); 27 | } 28 | } 29 | } 30 | }); 31 | }(numTimes)); 32 | 33 | return deferred; 34 | } 35 | 36 | function debounce(fn, wait) { 37 | var id; 38 | return function () { 39 | var args = arguments, context = this; 40 | 41 | // Cancel already in flight 42 | if (id) { 43 | window.clearTimeout(id); 44 | } 45 | 46 | window.setTimeout(function () { 47 | fn.apply(context, args); 48 | }, wait); 49 | } 50 | } 51 | 52 | function main () { 53 | var $input = $('#textInput'), 54 | $results = $('#results'); 55 | 56 | // Keep track of old state 57 | var jqXHR, 58 | currentText = ''; 59 | 60 | var handler = function () { 61 | var text = $(this).val(); 62 | 63 | // Cancel previous if one already out there 64 | if (jqXHR && jqXHR.state() === 'pending') { 65 | jqXHR.reject({ statusText: 'abort' }); 66 | } 67 | 68 | // Now check if text has changed 69 | if (text !== currentText) { 70 | currentText = text; 71 | 72 | $results.empty(); 73 | 74 | jqXHR = searchWikipedia(currentText, 3); 75 | 76 | jqXHR.then( 77 | function (data) { 78 | 79 | var result = data[1]; 80 | $.each(result, function (i, value) { 81 | $('
            • ' + value + '
            • ').appendTo($results); 82 | }); 83 | 84 | }, 85 | function (xhr) { 86 | if (xhr.statusText !== 'abort') { 87 | $('
            • ' + xhr.statusText + '
            • ').appendTo($results); 88 | } 89 | } 90 | ); 91 | } 92 | }; 93 | 94 | $input.keyup(debounce(handler, 500)); 95 | } 96 | 97 | $(function () { 98 | 99 | main(); 100 | 101 | }); 102 | 103 | }(this, jQuery)) 104 | 105 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/start/1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 1 9 | 10 | 11 | 12 | 13 | 14 |
              15 | 19 |
              20 |
              21 |
              22 | 23 | 24 |
              25 |
              26 |
              27 |
              28 |
                29 |
                30 |
                31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/start/1.js: -------------------------------------------------------------------------------- 1 | (function (window, $, undefined) { 2 | 3 | function main () { 4 | var $input = $('#textInput'), 5 | $results = $('#results'); 6 | 7 | // TODO: Get the key up behavior 8 | 9 | } 10 | 11 | $(function () { 12 | 13 | main(); 14 | 15 | }); 16 | 17 | }(this, jQuery)) 18 | 19 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/start/2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 2 9 | 10 | 11 | 12 | 13 | 14 |
                15 | 19 |
                20 |
                21 |
                22 | 23 | 24 |
                25 |
                26 |
                27 |
                28 |
                  29 |
                  30 |
                  31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/start/2.js: -------------------------------------------------------------------------------- 1 | (function (window, $, undefined) { 2 | 3 | function searchWikipedia (term) { 4 | var promise = $.ajax({ 5 | url: 'http://en.wikipedia.org/w/api.php', 6 | dataType: 'jsonp', 7 | data: { 8 | action: 'opensearch', 9 | format: 'json', 10 | search: encodeURI(term) 11 | } 12 | }); 13 | 14 | return promise; 15 | } 16 | 17 | function main () { 18 | var $input = $('#textInput'), 19 | $results = $('#results'); 20 | 21 | Rx.Observable.fromEvent($input, 'keyup') 22 | .map(function (e) { return $(e.target).val(); }) 23 | .flatMap(function (text) { 24 | 25 | $results.empty(); 26 | 27 | // TODO: Query wikipedia 28 | 29 | }); 30 | } 31 | 32 | $(function () { 33 | 34 | main(); 35 | 36 | }); 37 | 38 | }(this, jQuery)) 39 | 40 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/start/3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 3 9 | 10 | 11 | 12 | 13 | 14 |
                  15 | 19 |
                  20 |
                  21 |
                  22 | 23 | 24 |
                  25 |
                  26 |
                  27 |
                  28 |
                    29 |
                    30 |
                    31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/start/3.js: -------------------------------------------------------------------------------- 1 | (function (window, $, undefined) { 2 | 3 | function searchWikipedia (term) { 4 | var promise = $.ajax({ 5 | url: 'http://en.wikipedia.org/w/api.php', 6 | dataType: 'jsonp', 7 | data: { 8 | action: 'opensearch', 9 | format: 'json', 10 | search: encodeURI(term) 11 | } 12 | }); 13 | 14 | return Rx.Observable.fromPromise(promise); 15 | } 16 | 17 | function main () { 18 | var $input = $('#textInput'), 19 | $results = $('#results'); 20 | 21 | var subscription = Rx.Observable.fromEvent($input, 'keyup') 22 | .map(function (e) { return $(e.target).val(); }) 23 | .flatMap(function (text) { 24 | return searchWikipedia(text); 25 | }) 26 | // TODO: Ensure we cancel out of order requests!! 27 | .subscribe(function (data) { 28 | 29 | $results.empty(); 30 | 31 | var result = data[1]; 32 | $.each(result, function (i, value) { 33 | $('
                  • ' + value + '
                  • ').appendTo($results); 34 | }); 35 | 36 | }, function (xhr) { 37 | $results.empty(); 38 | 39 | $('
                  • ' + xhr.statusText + '
                  • ').appendTo($results); 40 | }); 41 | } 42 | 43 | $(function () { 44 | 45 | main(); 46 | 47 | }); 48 | 49 | }(this, jQuery)) 50 | 51 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/start/4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 4 9 | 10 | 11 | 12 | 13 | 14 |
                    15 | 19 |
                    20 |
                    21 |
                    22 | 23 | 24 |
                    25 |
                    26 |
                    27 |
                    28 |
                      29 |
                      30 |
                      31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/start/4.js: -------------------------------------------------------------------------------- 1 | // Add distinct 2 | 3 | (function (window, $, undefined) { 4 | 5 | function searchWikipedia (term) { 6 | var promise = $.ajax({ 7 | url: 'http://en.wikipedia.org/w/api.php', 8 | dataType: 'jsonp', 9 | data: { 10 | action: 'opensearch', 11 | format: 'json', 12 | search: encodeURI(term) 13 | } 14 | }); 15 | 16 | return Rx.Observable.fromPromise(promise); 17 | } 18 | 19 | function main () { 20 | var $input = $('#textInput'), 21 | $results = $('#results'); 22 | 23 | var subscription = Rx.Observable.fromEvent($input, 'keyup') 24 | .map(function (e) { return $(e.target).val(); }) 25 | .flatMapLatest(function (text) { 26 | return searchWikipedia(text); 27 | }) 28 | // TODO: Ensure only distinct values! 29 | .subscribe(function (data) { 30 | 31 | $results.empty(); 32 | 33 | var result = data[1]; 34 | $.each(result, function (i, value) { 35 | $('
                    • ' + value + '
                    • ').appendTo($results); 36 | }); 37 | 38 | }, function (xhr) { 39 | $results.empty(); 40 | 41 | $('
                    • ' + xhr.statusText + '
                    • ').appendTo($results); 42 | }); 43 | } 44 | 45 | $(function () { 46 | 47 | main(); 48 | 49 | }); 50 | 51 | }(this, jQuery)) 52 | 53 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/start/5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 5 9 | 10 | 11 | 12 | 13 | 14 |
                      15 | 19 |
                      20 |
                      21 |
                      22 | 23 | 24 |
                      25 |
                      26 |
                      27 |
                      28 |
                        29 |
                        30 |
                        31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/start/5.js: -------------------------------------------------------------------------------- 1 | // Add debounce/throttle 2 | 3 | (function (window, $, undefined) { 4 | 5 | function searchWikipedia (term) { 6 | var promise = $.ajax({ 7 | url: 'http://en.wikipedia.org/w/api.php', 8 | dataType: 'jsonp', 9 | data: { 10 | action: 'opensearch', 11 | format: 'json', 12 | search: encodeURI(term) 13 | } 14 | }).promise(); 15 | 16 | return Rx.Observable.fromPromise(promise); 17 | } 18 | 19 | function main () { 20 | var $input = $('#textInput'), 21 | $results = $('#results'); 22 | 23 | var subscription = Rx.Observable.fromEvent($input, 'keyup') 24 | .map(function (e) { return $(e.target).val(); }) 25 | .flatMapLatest(function (text) { 26 | return searchWikipedia(text); 27 | }) 28 | // TODO: Add debounce behavior to slow down requests 29 | .distinctUntilChanged() 30 | .subscribe(function (data) { 31 | 32 | $results.empty(); 33 | 34 | var result = data[1]; 35 | $.each(result, function (i, value) { 36 | $('
                      • ' + value + '
                      • ').appendTo($results); 37 | }); 38 | 39 | }, function (xhr) { 40 | $results.empty(); 41 | 42 | $('
                      • ' + xhr.statusText + '
                      • ').appendTo($results); 43 | }); 44 | } 45 | 46 | $(function () { 47 | 48 | main(); 49 | 50 | }); 51 | 52 | }(this, jQuery)) 53 | 54 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/start/6.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 1 9 | 10 | 11 | 12 | 13 | 14 |
                        15 | 19 |
                        20 |
                        21 |
                        22 | 23 | 24 |
                        25 |
                        26 |
                        27 |
                        28 |
                          29 |
                          30 |
                          31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lessons/autocomplete/rx/start/6.js: -------------------------------------------------------------------------------- 1 | (function (window, $, undefined) { 2 | 3 | function searchWikipedia (term) { 4 | var promise = $.ajax({ 5 | url: 'http://en.wikipedia.org/w/api.php', 6 | dataType: 'jsonp', 7 | data: { 8 | action: 'opensearch', 9 | format: 'json', 10 | search: encodeURI(term) 11 | } 12 | }).promise(); 13 | 14 | return Rx.Observable.fromPromise(promise); 15 | } 16 | 17 | function main () { 18 | var $input = $('#textInput'), 19 | $results = $('#results'); 20 | 21 | var subscription = Rx.Observable.fromEvent($input, 'keyup') 22 | .map(function (e) { return $(e.target).val(); }) 23 | 24 | // TODO: Add retry logic 25 | .flatMapLatest(function (text) { 26 | return searchWikipedia(text); 27 | }) 28 | 29 | .throttle(500) 30 | .distinctUntilChanged() 31 | .subscribe(function (data) { 32 | 33 | $results.empty(); 34 | 35 | var result = data[1]; 36 | $.each(result, function (i, value) { 37 | $('
                        • ' + value + '
                        • ').appendTo($results); 38 | }); 39 | 40 | }, function (xhr) { 41 | $results.empty(); 42 | 43 | $('
                        • ' + xhr.statusText + '
                        • ').appendTo($results); 44 | }); 45 | } 46 | 47 | $(function () { 48 | 49 | main(); 50 | 51 | }); 52 | 53 | }(this, jQuery)) 54 | 55 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/finish/1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 1 9 | 10 | 11 | 12 | 13 | 14 |
                          15 | 19 |
                          20 |
                          21 |
                          22 | 23 | 24 |
                          25 |
                          26 |
                          27 |
                          28 |
                            29 |
                            30 |
                            31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/finish/1.js: -------------------------------------------------------------------------------- 1 | (function (window, $, undefined) { 2 | 3 | function main () { 4 | var $input = $('#textInput'), 5 | $results = $('#results'); 6 | 7 | $input.keyup(function () { 8 | var text = $(this).val(); 9 | 10 | $results.empty(); 11 | 12 | $('
                          • ' + text + '
                          • ').appendTo($results); 13 | }); 14 | } 15 | 16 | $(function () { 17 | 18 | main(); 19 | 20 | }); 21 | 22 | }(this, jQuery)) 23 | 24 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/finish/2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 2 9 | 10 | 11 | 12 | 13 | 14 |
                            15 | 19 |
                            20 |
                            21 |
                            22 | 23 | 24 |
                            25 |
                            26 |
                            27 |
                            28 |
                              29 |
                              30 |
                              31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/finish/2.js: -------------------------------------------------------------------------------- 1 | (function (window, $, undefined) { 2 | 3 | function searchWikipedia (term) { 4 | return $.ajax({ 5 | url: 'http://en.wikipedia.org/w/api.php', 6 | dataType: 'jsonp', 7 | data: { 8 | action: 'opensearch', 9 | format: 'json', 10 | search: encodeURI(term) 11 | } 12 | }); 13 | } 14 | 15 | function main () { 16 | var $input = $('#textInput'), 17 | $results = $('#results'); 18 | 19 | $input.keyup(function () { 20 | var text = $(this).val(); 21 | 22 | $results.empty(); 23 | 24 | searchWikipedia(text).then( 25 | function (data) { 26 | 27 | var result = data[1]; 28 | $.each(result, function (i, value) { 29 | $('
                            • ' + value + '
                            • ').appendTo($results); 30 | }); 31 | 32 | }, 33 | function (err) { 34 | $('
                            • ' + err + '
                            • ').appendTo($results); 35 | } 36 | ); 37 | }); 38 | } 39 | 40 | $(function () { 41 | 42 | main(); 43 | 44 | }); 45 | 46 | }(this, jQuery)) 47 | 48 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/finish/3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 3 9 | 10 | 11 | 12 | 13 | 14 |
                              15 | 19 |
                              20 |
                              21 |
                              22 | 23 | 24 |
                              25 |
                              26 |
                              27 |
                              28 |
                                29 |
                                30 |
                                31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/finish/3.js: -------------------------------------------------------------------------------- 1 | (function (window, $, undefined) { 2 | 3 | function searchWikipedia (term) { 4 | return $.ajax({ 5 | url: 'http://en.wikipedia.org/w/api.php', 6 | dataType: 'jsonp', 7 | data: { 8 | action: 'opensearch', 9 | format: 'json', 10 | search: term 11 | } 12 | }); 13 | } 14 | 15 | function main () { 16 | var $input = $('#textInput'), 17 | $results = $('#results'); 18 | 19 | // Keep track of old state 20 | var jqXHR; 21 | 22 | $input.keyup(function () { 23 | var text = $(this).val(); 24 | 25 | $results.empty(); 26 | 27 | // Cancel previous if one already out there 28 | if (jqXHR && jqXHR.state() === 'pending') { 29 | jqXHR.abort(); 30 | } 31 | 32 | jqXHR = searchWikipedia(text); 33 | 34 | jqXHR.then( 35 | function (data) { 36 | 37 | var result = data[1]; 38 | $.each(result, function (i, value) { 39 | $('
                              • ' + value + '
                              • ').appendTo($results); 40 | }); 41 | 42 | }, 43 | function (xhr) { 44 | if (xhr.statusText !== 'abort') { 45 | $('
                              • ' + xhr.statusText + '
                              • ').appendTo($results); 46 | } 47 | 48 | 49 | } 50 | ); 51 | }); 52 | } 53 | 54 | $(function () { 55 | 56 | main(); 57 | 58 | }); 59 | 60 | }(this, jQuery)) 61 | 62 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/finish/4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 4 9 | 10 | 11 | 12 | 13 | 14 |
                                15 | 19 |
                                20 |
                                21 |
                                22 | 23 | 24 |
                                25 |
                                26 |
                                27 |
                                28 |
                                  29 |
                                  30 |
                                  31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/finish/4.js: -------------------------------------------------------------------------------- 1 | // Add distinct 2 | 3 | (function (window, $, undefined) { 4 | 5 | function searchWikipedia (term) { 6 | return $.ajax({ 7 | url: 'http://en.wikipedia.org/w/api.php', 8 | dataType: 'jsonp', 9 | data: { 10 | action: 'opensearch', 11 | format: 'json', 12 | search: encodeURI(term) 13 | } 14 | }); 15 | } 16 | 17 | function main () { 18 | var $input = $('#textInput'), 19 | $results = $('#results'); 20 | 21 | // Keep track of old state 22 | var jqXHR, 23 | currentText = ''; 24 | 25 | $input.keyup(function () { 26 | var text = $(this).val(); 27 | 28 | // Cancel previous if one already out there 29 | if (jqXHR && jqXHR.state() === 'pending') { 30 | jqXHR.abort(); 31 | } 32 | 33 | // Now check if text has changed 34 | if (text !== currentText) { 35 | currentText = text; 36 | 37 | $results.empty(); 38 | 39 | jqXHR = searchWikipedia(currentText); 40 | 41 | jqXHR.then( 42 | function (data) { 43 | 44 | var result = data[1]; 45 | $.each(result, function (i, value) { 46 | $('
                                • ' + value + '
                                • ').appendTo($results); 47 | }); 48 | 49 | }, 50 | function (xhr) { 51 | if (xhr.statusText !== 'abort') { 52 | $('
                                • ' + xhr.statusText + '
                                • ').appendTo($results); 53 | } 54 | } 55 | ); 56 | } 57 | 58 | 59 | }); 60 | } 61 | 62 | $(function () { 63 | 64 | main(); 65 | 66 | }); 67 | 68 | }(this, jQuery)) 69 | 70 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/finish/5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 5 9 | 10 | 11 | 12 | 13 | 14 |
                                  15 | 19 |
                                  20 |
                                  21 |
                                  22 | 23 | 24 |
                                  25 |
                                  26 |
                                  27 |
                                  28 |
                                    29 |
                                    30 |
                                    31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/finish/5.js: -------------------------------------------------------------------------------- 1 | // Add debounce/throttle 2 | 3 | (function (window, $, undefined) { 4 | 5 | function searchWikipedia (term) { 6 | return $.ajax({ 7 | url: 'http://en.wikipedia.org/w/api.php', 8 | dataType: 'jsonp', 9 | data: { 10 | action: 'opensearch', 11 | format: 'json', 12 | search: term 13 | } 14 | }); 15 | } 16 | 17 | function debounce(fn, wait) { 18 | var id; 19 | return function () { 20 | var args = arguments, context = this; 21 | 22 | // Cancel already in flight 23 | if (id) { 24 | window.clearTimeout(id); 25 | } 26 | 27 | window.setTimeout(function () { 28 | fn.apply(context, args); 29 | }, wait); 30 | } 31 | } 32 | 33 | function main () { 34 | var $input = $('#textInput'), 35 | $results = $('#results'); 36 | 37 | // Keep track of old state 38 | var jqXHR, 39 | currentText = ''; 40 | 41 | var handler = function () { 42 | var text = $(this).val(); 43 | 44 | // Cancel previous if one already out there 45 | if (jqXHR && jqXHR.state() === 'pending') { 46 | jqXHR.abort(); 47 | } 48 | 49 | // Now check if text has changed 50 | if (text !== currentText) { 51 | currentText = text; 52 | 53 | $results.empty(); 54 | 55 | jqXHR = searchWikipedia(currentText); 56 | 57 | jqXHR.then( 58 | function (data) { 59 | 60 | var result = data[1]; 61 | $.each(result, function (i, value) { 62 | $('
                                  • ' + value + '
                                  • ').appendTo($results); 63 | }); 64 | 65 | }, 66 | function (xhr) { 67 | if (xhr.statusText !== 'abort') { 68 | $('
                                  • ' + xhr.statusText + '
                                  • ').appendTo($results); 69 | } 70 | } 71 | ); 72 | } 73 | }; 74 | 75 | $input.keyup(debounce(handler, 500)); 76 | } 77 | 78 | $(function () { 79 | 80 | main(); 81 | 82 | }); 83 | 84 | }(this, jQuery)) 85 | 86 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/finish/6.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 6 9 | 10 | 11 | 12 | 13 | 14 |
                                    15 | 19 |
                                    20 |
                                    21 |
                                    22 | 23 | 24 |
                                    25 |
                                    26 |
                                    27 |
                                    28 |
                                      29 |
                                      30 |
                                      31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/finish/6.js: -------------------------------------------------------------------------------- 1 | // Add debounce/throttle 2 | 3 | (function (window, $, undefined) { 4 | 5 | function searchWikipedia (term, numTimes) { 6 | var deferred = $.Deferred(); 7 | 8 | (function makeRequest(num) { 9 | $.ajax({ 10 | url: 'http://en.wikipedia.org/w/api.php', 11 | dataType: 'jsonp', 12 | data: { 13 | action: 'opensearch', 14 | format: 'json', 15 | search: term 16 | }, 17 | success: function (data, status, xhr) { 18 | deferred.resolve(data, status, xhr); 19 | }, 20 | error: function (xhr) { 21 | if (xhr.statusText !== 'abort') { 22 | if (num > 0) { 23 | return makeRequest(num - 1); 24 | } else { 25 | deferred.rejectWith(this, arguments); 26 | } 27 | } 28 | } 29 | }); 30 | }(numTimes)); 31 | 32 | return deferred; 33 | } 34 | 35 | function debounce(fn, wait) { 36 | var id; 37 | return function () { 38 | var args = arguments, context = this; 39 | 40 | // Cancel already in flight 41 | if (id) { 42 | window.clearTimeout(id); 43 | } 44 | 45 | window.setTimeout(function () { 46 | fn.apply(context, args); 47 | }, wait); 48 | } 49 | } 50 | 51 | function main () { 52 | var $input = $('#textInput'), 53 | $results = $('#results'); 54 | 55 | // Keep track of old state 56 | var jqXHR, 57 | currentText = ''; 58 | 59 | var handler = function () { 60 | var text = $(this).val(); 61 | 62 | // Cancel previous if one already out there 63 | if (jqXHR && jqXHR.state() === 'pending') { 64 | jqXHR.reject({ statusText: 'abort' }); 65 | } 66 | 67 | // Now check if text has changed 68 | if (text !== currentText) { 69 | currentText = text; 70 | 71 | $results.empty(); 72 | 73 | jqXHR = searchWikipedia(currentText, 3); 74 | 75 | jqXHR.then( 76 | function (data) { 77 | 78 | var result = data[1]; 79 | $.each(result, function (i, value) { 80 | $('
                                    • ' + value + '
                                    • ').appendTo($results); 81 | }); 82 | 83 | }, 84 | function (xhr) { 85 | if (xhr.statusText !== 'abort') { 86 | $('
                                    • ' + xhr.statusText + '
                                    • ').appendTo($results); 87 | } 88 | } 89 | ); 90 | } 91 | }; 92 | 93 | $input.keyup(debounce(handler, 500)); 94 | } 95 | 96 | $(function () { 97 | 98 | main(); 99 | 100 | }); 101 | 102 | }(this, jQuery)) 103 | 104 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/start/1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 1 9 | 10 | 11 | 12 | 13 | 14 |
                                      15 | 19 |
                                      20 |
                                      21 |
                                      22 | 23 | 24 |
                                      25 |
                                      26 |
                                      27 |
                                      28 |
                                        29 |
                                        30 |
                                        31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/start/1.js: -------------------------------------------------------------------------------- 1 | (function (window, $, undefined) { 2 | 3 | function main () { 4 | var $input = $('#textInput'), 5 | $results = $('#results'); 6 | 7 | // Get the key up behavior 8 | 9 | } 10 | 11 | $(function () { 12 | 13 | main(); 14 | 15 | }); 16 | 17 | }(this, jQuery)) 18 | 19 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/start/2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 2 9 | 10 | 11 | 12 | 13 | 14 |
                                        15 | 19 |
                                        20 |
                                        21 |
                                        22 | 23 | 24 |
                                        25 |
                                        26 |
                                        27 |
                                        28 |
                                          29 |
                                          30 |
                                          31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/start/2.js: -------------------------------------------------------------------------------- 1 | (function (window, $, undefined) { 2 | 3 | function searchWikipedia (term) { 4 | return $.ajax({ 5 | url: 'http://en.wikipedia.org/w/api.php', 6 | dataType: 'jsonp', 7 | data: { 8 | action: 'opensearch', 9 | format: 'json', 10 | search: encodeURI(term) 11 | } 12 | }); 13 | } 14 | 15 | function main () { 16 | var $input = $('#textInput'), 17 | $results = $('#results'); 18 | 19 | $input.keyup(function () { 20 | var text = $(this).val(); 21 | 22 | $results.empty(); 23 | 24 | // Query wikipedia 25 | }); 26 | } 27 | 28 | $(function () { 29 | 30 | main(); 31 | 32 | }); 33 | 34 | }(this, jQuery)) 35 | 36 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/start/3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 3 9 | 10 | 11 | 12 | 13 | 14 |
                                          15 | 19 |
                                          20 |
                                          21 |
                                          22 | 23 | 24 |
                                          25 |
                                          26 |
                                          27 |
                                          28 |
                                            29 |
                                            30 |
                                            31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/start/3.js: -------------------------------------------------------------------------------- 1 | (function (window, $, undefined) { 2 | 3 | function searchWikipedia (term) { 4 | return $.ajax({ 5 | url: 'http://en.wikipedia.org/w/api.php', 6 | dataType: 'jsonp', 7 | data: { 8 | action: 'opensearch', 9 | format: 'json', 10 | search: encodeURI(term) 11 | } 12 | }); 13 | } 14 | 15 | function main () { 16 | var $input = $('#textInput'), 17 | $results = $('#results'); 18 | 19 | // Keep track of old state 20 | var jqXHR; 21 | 22 | $input.keyup(function () { 23 | var text = $(this).val(); 24 | 25 | $results.empty(); 26 | 27 | // Cancel previous if one already out there 28 | 29 | jqXHR = searchWikipedia(text); 30 | 31 | jqXHR.then( 32 | function (data) { 33 | 34 | var result = data[1]; 35 | $.each(result, function (i, value) { 36 | $('
                                          • ' + value + '
                                          • ').appendTo($results); 37 | }); 38 | 39 | }, 40 | function (xhr) { 41 | 42 | // Handle any aborts as ok operations 43 | $('
                                          • ' + xhr.statusText + '
                                          • ').appendTo($results); 44 | } 45 | ); 46 | }); 47 | } 48 | 49 | $(function () { 50 | 51 | main(); 52 | 53 | }); 54 | 55 | }(this, jQuery)) 56 | 57 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/start/4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 1 9 | 10 | 11 | 12 | 13 | 14 |
                                            15 | 19 |
                                            20 |
                                            21 |
                                            22 | 23 | 24 |
                                            25 |
                                            26 |
                                            27 |
                                            28 |
                                              29 |
                                              30 |
                                              31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/start/4.js: -------------------------------------------------------------------------------- 1 | // Add distinct 2 | 3 | (function (window, $, undefined) { 4 | 5 | function searchWikipedia (term) { 6 | return $.ajax({ 7 | url: 'http://en.wikipedia.org/w/api.php', 8 | dataType: 'jsonp', 9 | data: { 10 | action: 'opensearch', 11 | format: 'json', 12 | search: encodeURI(term) 13 | } 14 | }); 15 | } 16 | 17 | function main () { 18 | var $input = $('#textInput'), 19 | $results = $('#results'); 20 | 21 | // Keep track of old state 22 | var jqXHR, 23 | currentText = ''; 24 | 25 | $input.keyup(function () { 26 | var text = $(this).val(); 27 | 28 | // Cancel previous if one already out there 29 | if (jqXHR && jqXHR.state() === 'pending') { 30 | jqXHR.abort(); 31 | } 32 | 33 | // Now check if text has changed 34 | 35 | $results.empty(); 36 | 37 | jqXHR = searchWikipedia(currentText); 38 | 39 | jqXHR.then( 40 | function (data) { 41 | 42 | var result = data[1]; 43 | $.each(result, function (i, value) { 44 | $('
                                            • ' + value + '
                                            • ').appendTo($results); 45 | }); 46 | 47 | }, 48 | function (err) { 49 | if (jqXHR.statusText !== 'abort') { 50 | $('
                                            • ' + jqXHR.statusText + '
                                            • ').appendTo($results); 51 | } 52 | } 53 | ); 54 | 55 | 56 | }); 57 | } 58 | 59 | $(function () { 60 | 61 | main(); 62 | 63 | }); 64 | 65 | }(this, jQuery)) 66 | 67 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/start/5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 1 9 | 10 | 11 | 12 | 13 | 14 |
                                              15 | 19 |
                                              20 |
                                              21 |
                                              22 | 23 | 24 |
                                              25 |
                                              26 |
                                              27 |
                                              28 |
                                                29 |
                                                30 |
                                                31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/start/5.js: -------------------------------------------------------------------------------- 1 | // Add debounce/throttle 2 | 3 | (function (window, $, undefined) { 4 | 5 | function searchWikipedia (term) { 6 | return $.ajax({ 7 | url: 'http://en.wikipedia.org/w/api.php', 8 | dataType: 'jsonp', 9 | data: { 10 | action: 'opensearch', 11 | format: 'json', 12 | search: encodeURI(term) 13 | } 14 | }); 15 | } 16 | 17 | function debounce(fn, wait) { 18 | var id; 19 | return function () { 20 | var args = arguments, context = this; 21 | 22 | // Cancel already in flight 23 | if (id) { 24 | window.clearTimeout(id); 25 | } 26 | 27 | window.setTimeout(function () { 28 | fn.apply(context, args); 29 | }, wait); 30 | } 31 | } 32 | 33 | function main () { 34 | var $input = $('#textInput'), 35 | $results = $('#results'); 36 | 37 | // Keep track of old state 38 | var jqXHR, 39 | currentText = ''; 40 | 41 | var handler = function () { 42 | var text = $(this).val(); 43 | 44 | // Cancel previous if one already out there 45 | if (jqXHR && jqXHR.state() === 'pending') { 46 | jqXHR.abort(); 47 | } 48 | 49 | // Now check if text has changed 50 | if (text !== currentText) { 51 | currentText = text; 52 | 53 | $results.empty(); 54 | 55 | jqXHR = searchWikipedia(currentText); 56 | 57 | jqXHR.then( 58 | function (data) { 59 | 60 | var result = data[1]; 61 | $.each(result, function (i, value) { 62 | $('
                                              • ' + value + '
                                              • ').appendTo($results); 63 | }); 64 | 65 | }, 66 | function (err) { 67 | if (jqXHR.statusText !== 'abort') { 68 | $('
                                              • ' + xhr.statusText + '
                                              • ').appendTo($results); 69 | } 70 | } 71 | ); 72 | } 73 | }; 74 | 75 | // Handle debounce for our key up behavior 76 | } 77 | 78 | $(function () { 79 | 80 | main(); 81 | 82 | }); 83 | 84 | }(this, jQuery)) 85 | 86 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/start/6.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exercise 1 9 | 10 | 11 | 12 | 13 | 14 |
                                                15 | 19 |
                                                20 |
                                                21 |
                                                22 | 23 | 24 |
                                                25 |
                                                26 |
                                                27 |
                                                28 |
                                                  29 |
                                                  30 |
                                                  31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lessons/autocomplete/without_rx/start/6.js: -------------------------------------------------------------------------------- 1 | // Add debounce/throttle 2 | 3 | (function (window, $, undefined) { 4 | 5 | // Add retry logic here 6 | function searchWikipedia (term) { 7 | return $.ajax({ 8 | url: 'http://en.wikipedia.org/w/api.php', 9 | dataType: 'jsonp', 10 | data: { 11 | action: 'opensearch', 12 | format: 'json', 13 | search: encodeURI(term) 14 | } 15 | }); 16 | } 17 | 18 | function debounce(fn, wait) { 19 | var id; 20 | return function () { 21 | var args = arguments, context = this; 22 | 23 | // Cancel already in flight 24 | if (id) { 25 | window.clearTimeout(id); 26 | } 27 | 28 | window.setTimeout(function () { 29 | fn.apply(context, args); 30 | }, wait); 31 | } 32 | } 33 | 34 | function main () { 35 | var $input = $('#textInput'), 36 | $results = $('#results'); 37 | 38 | // Keep track of old state 39 | var jqXHR, 40 | currentText = ''; 41 | 42 | var handler = function () { 43 | var text = $(this).val(); 44 | 45 | // Cancel previous if one already out there 46 | if (jqXHR && jqXHR.state() === 'pending') { 47 | jqXHR.reject({ statusText: 'abort' }); 48 | } 49 | 50 | // Now check if text has changed 51 | if (text !== currentText) { 52 | currentText = text; 53 | 54 | $results.empty(); 55 | 56 | jqXHR = searchWikipedia(currentText, 3); 57 | 58 | jqXHR.then( 59 | function (data) { 60 | 61 | var result = data[1]; 62 | $.each(result, function (i, value) { 63 | $('
                                                • ' + value + '
                                                • ').appendTo($results); 64 | }); 65 | 66 | }, 67 | function (xhr) { 68 | if (xhr.statusText !== 'abort') { 69 | $('
                                                • ' + xhr.statusText + '
                                                • ').appendTo($results); 70 | } 71 | } 72 | ); 73 | } 74 | }; 75 | 76 | $input.keyup(debounce(handler, 500)); 77 | } 78 | 79 | $(function () { 80 | 81 | main(); 82 | 83 | }); 84 | 85 | }(this, jQuery)) 86 | 87 | -------------------------------------------------------------------------------- /lessons/binding/binding.css: -------------------------------------------------------------------------------- 1 | .tko-example { 2 | border-left: 2px solid #ace; 3 | border-radius: 5px; 4 | padding-left: 8px; 5 | position: relative; 6 | left: -10px; 7 | } 8 | 9 | .tko-example .tko-example-code { 10 | display: none; 11 | } 12 | 13 | .tko-list-pills { 14 | margin: 10px; 15 | } 16 | 17 | .tko-list-pills li { 18 | border: 1px solid #999; 19 | border-radius: 5px; 20 | padding: 0px 7px; 21 | margin: 2px; 22 | display: inline-block; 23 | } 24 | 25 | .tko-list-pills li:hover { 26 | color: white; 27 | background: black; 28 | border-color: white; 29 | cursor: pointer; 30 | } -------------------------------------------------------------------------------- /lessons/binding/binding.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | TKO knocks you out technically! 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
                                                  22 |

                                                  TKO

                                                  23 | 24 |
                                                  25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
                                                  33 |
                                                  34 | 35 | 36 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /lessons/binding/binding.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | var freeExports = typeof exports == 'object' && exports, 3 | freeModule = typeof module == 'object' && module && module.exports == freeExports && module, 4 | freeGlobal = typeof global == 'object' && global; 5 | if (freeGlobal.global === freeGlobal) { 6 | window = freeGlobal; 7 | } 8 | 9 | // Because of build optimizers 10 | if (typeof define === 'function' && define.amd) { 11 | define(['rx', 'exports'], function (Rx, exports) { 12 | root.tko = factory(root, exports, Rx); 13 | return root.tko; 14 | }); 15 | } else if (typeof module === 'object' && module && module.exports === freeExports) { 16 | module.exports = factory(root, module.exports, require('rx')); 17 | } else { 18 | root.tko = factory(root, {}, root.Rx); 19 | } 20 | }(this, function (global, exp, Rx, undefined) { 21 | 22 | var Observer = Rx.Observer, 23 | Observable = Rx.Observable, 24 | observableCreate = Rx.Observable.create, 25 | fromEvent = Rx.Observable.fromEvent, 26 | BehaviorSubject = Rx.BehaviorSubject, 27 | Subject = Rx.Subject, 28 | CompositeDisposable = Rx.CompositeDisposable, 29 | disposableCreate = Rx.Disposable.create, 30 | disposableEmpty = Rx.Disposable.empty, 31 | timeoutScheduler = Rx.Scheduler.timeout, 32 | inherits = Rx.Internals.inherits, 33 | splice = Array.prototype.splice; 34 | 35 | var sub = 'subscribe', 36 | fn = 'function', 37 | onNext = 'onNext'; 38 | 39 | function noop () { } 40 | 41 | var tko = { }; 42 | 43 | var ObservableArray = (function (_super) { 44 | 45 | function ObservableArray(items) { 46 | this.values = []; 47 | this.lifetimes = []; 48 | for (var i = 0, len = items.length; i < len; i++) { 49 | this.push(items[i]); 50 | } 51 | 52 | _super.call(this); 53 | } 54 | 55 | inherits(ObservableArray, _super); 56 | 57 | var observableArrayPrototype = ObservableArray.prototype; 58 | 59 | observableArrayPrototype.subscribe = function (observerOrOnNext) { 60 | observerOrOnNext || (observerOrOnNext = noop); 61 | var subscription = _super.prototype.subscribe.apply(this, arguments); 62 | 63 | this.purge(); 64 | var obsFunc = typeof observerOrOnNext === fn ? 65 | observerOrOnNext : 66 | observerOrOnNext.onNext; 67 | 68 | for(var i = 0, len = this.lifetimes.length; i < len; i++) { 69 | obsFunc(this.lifetimes[i]); 70 | } 71 | 72 | return subscription; 73 | }; 74 | 75 | observableArrayPrototype.push = function (item) { 76 | this.values.push(value); 77 | var lifetime = new BehaviorSubject(value); 78 | this.lifetimes.push(lifetime); 79 | this.onNext(lifetime); 80 | 81 | return this.values.length; 82 | }; 83 | 84 | observableArrayPrototype.remove = function (value) { 85 | var index = this.values.indexOf(value); 86 | 87 | this.splice(index, 1); 88 | 89 | return index !== -1; 90 | }; 91 | 92 | observableArrayPrototype.splice = function () { 93 | splice.apply(this.values, arguments); 94 | var removed = spliced.apply(this.lifetimes, arguments); 95 | 96 | for (var i = 0, len = removed.length; i < len; i++) { 97 | removed[i].onCompleted(); 98 | } 99 | }; 100 | 101 | observableArrayPrototype.dispose = function () { 102 | for (var i = 0, len = this.lifetimes.length; i < len; i++) { 103 | this.lifetimes[i].onCompleted(); 104 | } 105 | }; 106 | 107 | observableArrayPrototype.purge = function () { 108 | for (var i = 0, len = this.lifetimes.length; i < len; i++) { 109 | var lifetime = this.lifetimes[i]; 110 | if (lifetime.isCompleted) { 111 | this.remove(lifetime); 112 | } 113 | } 114 | }; 115 | 116 | return ObservableArray; 117 | }(Subject)); 118 | 119 | tko.binders = { 120 | attr: function (target, context, options) { 121 | var disposable = new CompositeDisposable(); 122 | 123 | for (var key in options) { 124 | (function (key) { 125 | var obsOrValue = options[key]; 126 | disposable.add(tko.utils.applyBindings(obsOrValue, function (x) { 127 | target.attr(key, x); 128 | })); 129 | }(key)); 130 | } 131 | 132 | return disposable; 133 | }, 134 | checked: function (target, context, obsOrValue) { 135 | var disposable = new CompositeDisposable(); 136 | if (onNext in obsOrValue) { 137 | var observer = obsOrValue; 138 | 139 | disposable.add(fromEvent(target, 'change') 140 | .map(function () { 141 | return target.prop('checked'); 142 | }) 143 | .subscribe(observer.onNext.bind(observer))); 144 | } 145 | disposable.add(tko.utils.applyBindings(obsOrValue, function (x) { 146 | target.prop('checked', x); 147 | })); 148 | 149 | return disposable; 150 | }, 151 | click: function (target, context, options) { 152 | return tko.binders.event(target, context, options, 'click'); 153 | }, 154 | css: function (target, context, options) { 155 | var disposable = new CompositeDisposable(); 156 | 157 | for (var key in options) { 158 | (function (key) { 159 | disposable.add(tko.utils.applyBindings(options[key], function (x) { 160 | target.toggleClass(css, x); 161 | })); 162 | }(key)); 163 | } 164 | 165 | return disposable; 166 | }, 167 | event: function (target, context, options, type) { 168 | type || (type = options.type); 169 | var obs = fromEvent(target, type); 170 | 171 | return obs.subscribe(function (e) { 172 | var opts = { 173 | target: target, 174 | context: context, 175 | e: e 176 | }; 177 | if (typeof options === fn) { 178 | options(opts); 179 | } else { 180 | options.onNext(opts); 181 | } 182 | }); 183 | }, 184 | foreach: function (target, context, obsArray) { 185 | var disposable = new CompositeDisposable(); 186 | 187 | var template = target.html().trim(); 188 | 189 | disposable.add(disposableCreate(function () { 190 | target.empty().append(template); 191 | })); 192 | 193 | timeoutScheduler.schedule(function () { 194 | disposable.add(obsArray.subscribe(function (lifetime) { 195 | var sub, 196 | disposer, 197 | child = $(template).appendTo(target), 198 | binding = tko.internal.applyBindings(child, { 199 | viewModel: lifetime.value, 200 | viewModelRoot: context. viewModelRoot, 201 | viewModelParent: context.viewModel 202 | }), 203 | 204 | dispose = function () { 205 | child.remove(); 206 | disposable.remove(binding); 207 | disposable.remove(disposer); 208 | disposable.remove(sub); 209 | }, 210 | 211 | disposer = disposableCreate(dispose), 212 | 213 | sub = lifetime.subscribe(noop, dispose, dispose); 214 | 215 | disposable.add(binding); 216 | disposable.add(disposer); 217 | disposable.add(sub); 218 | })); 219 | }); 220 | 221 | return disposable; 222 | }, 223 | html: function (target, context, obsOrValue) { 224 | return tko.utils.applyBindings(obsOrValue, target.html.bind(target)); 225 | }, 226 | text: function (target, context, obsOrValue) { 227 | return tko.utils.applyBindings(obsOrValue, target.text.bind(target)); 228 | }, 229 | value: function (target, context, options) { 230 | var disposable = new CompositeDisposable(); 231 | 232 | var options = tko.utils.parseBindingOptions(options); 233 | if (options.on && options.on.indexOf('after') === 0) { 234 | options.on = options.on.slice(5); 235 | options.delay = true; 236 | } 237 | if (typeof options.source.onNext === fn) { 238 | var observer = options.source, 239 | getObs = fromEvent(target, options.on || 'change'); 240 | 241 | if (options.delay) { 242 | getObs = getObs.delay(0); 243 | } 244 | 245 | disposable.add(getObs 246 | .map(function () { 247 | return target.val(); 248 | }) 249 | .subscribe(observer.onNext.bind(observer)) 250 | ); 251 | } 252 | 253 | if (options.source instanceof Observable) { 254 | var focus = fromEvent(target, 'focus'), 255 | blur = fromEvent(target,'blur'); 256 | 257 | options.source = options.source.takeUntil(focus).concat(blur.take(1)).repeat(); 258 | } 259 | 260 | disposable.add(tko.utils.applyBindings(options.source, target.val.bind(target))); 261 | 262 | return disposable; 263 | }, 264 | visible: function (target, context, options) { 265 | return tko.utils.applyBindings(obsOrValue, function (x) { 266 | taget.css(x ? '' : 'none'); 267 | }); 268 | } 269 | }; 270 | 271 | tko.utils = { 272 | applyBindings: function (obsOrValue, cb) { 273 | if (sub in obsOrValue) { 274 | return obsOrValue.subscribe(cb); 275 | } 276 | cb(obsOrValue); 277 | return disposableEmpty; 278 | }, 279 | wrap: function (valueOrBehavior) { 280 | return sub in valueOrBehavior ? 281 | valueOrBehavior : 282 | new BehaviorSubject(valueOrBehavior); 283 | }, 284 | parseBindingOptions: function (param, options) { 285 | options || (options = {}); 286 | if (typeof param === sub || onNext in param || sub in param) { 287 | options.source = param; 288 | return options; 289 | } 290 | for (var prop in param) { 291 | options[prop] = param[prop]; 292 | } 293 | return options; 294 | }, 295 | toJSON: function (obj) { 296 | return JSON.stringify(function (s, field) { 297 | if (field instanceof ObservableArray) { 298 | return field.values; 299 | } 300 | if (field instanceof Observable) { 301 | return field.value; 302 | } 303 | if (field instanceof Observer) { 304 | return undefined; 305 | } 306 | return field; 307 | }); 308 | }, 309 | unwrap: function (valueOrBehavior) { 310 | return 'value' in valueOrBehavior && sub in valueOrBehavior ? 311 | valueOrBehavior.value : 312 | valueOrBehavior; 313 | } 314 | }; 315 | 316 | tko.internal = { 317 | applyBindings: function (target, context) { 318 | var bindings = tko.internal.parseBindings(target, context), 319 | disposable = new CompositeDisposable(); 320 | for (var binder in bindings) { 321 | disposable.add(tko.binders[binder](target, context, bindings[binder])); 322 | } 323 | 324 | target.children().each(function () { 325 | disposable.add(tko.internal.applyBindings($(this), context)); 326 | }); 327 | 328 | return disposable; 329 | }, 330 | parseBindings: function (target, context) { 331 | var binding = target.attr('data-tko'); 332 | if (!binding) { 333 | return null; 334 | } 335 | var keys = ['$data', '$root', '$parent'], 336 | values = [context.viewModel, context.viewModelRoot, context.viewModelParent]; 337 | for (var key in context.viewModel) { 338 | keys.push(key); 339 | values.push(context.viewModel[key]); 340 | } 341 | 342 | return new Function(keys, 'return { ' + binding + ' };').apply(null, values); 343 | } 344 | }; 345 | 346 | tko.applyBindings = function (vm, target) { 347 | target || (target = $(window.document.body)); 348 | return tko.internal.applyBindings(target, { 349 | viewModel: vm, 350 | viewModelRoot: vm, 351 | viewModelParent: null 352 | }); 353 | }; 354 | 355 | tko.observableArray = function (items) { 356 | if (!arguments.length) { 357 | items = []; 358 | } 359 | return new ObservableArray(items); 360 | }; 361 | 362 | tko.behavior = function (value) { 363 | return new Rx.BehaviorSubject(value); 364 | }; 365 | 366 | /** 367 | * var obs = tko.computed({ 368 | * params: { 369 | * a: Rx.Observable.interval(100), 370 | * b: Rx.Observable.interval(250), 371 | * c: Rx.Observable.interval(75), 372 | * d: Rx.Observable.interval(650) 373 | * }, 374 | * read: function (params) { 375 | * return params.a + ":" + params.b + ":" + params.c + ":" + params.d; 376 | * } 377 | * }); 378 | * 379 | * obs.subscribe(function (x) { 380 | * console.log(x); 381 | * }); 382 | */ 383 | tko.computed = function (options) { 384 | var keys = [], values = []; 385 | for (var prop in options.params) { 386 | keys.push(prop); 387 | values.push(tko.utils.wrap(options.params[prop])); 388 | } 389 | 390 | var source = Rx.Observable.combineLatest(values, function () { 391 | var args = arguments, 392 | params = {}; 393 | for (var i = 0, len = keys.length; i < len; i++) { 394 | params[keys[i]] = args[i]; 395 | } 396 | return params; 397 | }); 398 | 399 | return observableCreate(function (o) { 400 | return source.map(options.read).subscribe(o); 401 | }); 402 | }; 403 | 404 | return tko; 405 | 406 | })); -------------------------------------------------------------------------------- /lessons/schedulers/draw.css: -------------------------------------------------------------------------------- 1 | html { background: #000; } 2 | canvas { display: block; } -------------------------------------------------------------------------------- /lessons/schedulers/finish.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Rx for JavaScript Rocks! 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /lessons/schedulers/finish.js: -------------------------------------------------------------------------------- 1 | (function (window, undefined) { 2 | 3 | var freeExports = typeof exports == 'object' && exports, 4 | freeModule = typeof module == 'object' && module && module.exports == freeExports && module, 5 | freeGlobal = typeof global == 'object' && global; 6 | if (freeGlobal.global === freeGlobal) { 7 | window = freeGlobal; 8 | } 9 | 10 | var canvas, context, subscription; 11 | 12 | function init() { 13 | 14 | var host = document.body; 15 | 16 | canvas = document.createElement( 'canvas' ); 17 | 18 | // ie IE < 9 19 | if (typeof G_vmlCanvasManager !== 'undefined') { 20 | G_vmlCanvasManager.initElement(canvas); 21 | } 22 | 23 | canvas.width = 512; 24 | canvas.height = 512; 25 | 26 | context = canvas.getContext( '2d' ); 27 | 28 | host.appendChild( canvas ); 29 | } 30 | 31 | function createAnimation(scheduler) { 32 | scheduler || (scheduler = Rx.Scheduler.requestAnimationFrame); 33 | 34 | return Rx.Observable.generate( 35 | 0, 36 | function (x) { return true; }, 37 | function (x) { return x + 1; }, 38 | function (x) { return x; }, 39 | scheduler 40 | ) 41 | .timestamp(); 42 | } 43 | 44 | function draw(ts) { 45 | 46 | var time = ts.timestamp * 0.002; 47 | var x = Math.sin( time ) * 192 + 256; 48 | var y = Math.cos( time * 0.9 ) * 192 + 256; 49 | 50 | context.fillStyle = ts.value % 2 === 0 ? 'rgb(200,200,20)' : 'rgb(20,20,200)'; 51 | context.beginPath(); 52 | context.arc( x, y, 10, 0, Math.PI * 2, true ); 53 | context.closePath(); 54 | context.fill(); 55 | } 56 | 57 | function main () { 58 | init(); 59 | 60 | subscription = createAnimation().subscribe(draw); 61 | } 62 | 63 | // Exports 64 | window.main = main; 65 | window.createAnimation = createAnimation; 66 | }(window)); -------------------------------------------------------------------------------- /lessons/schedulers/requestanimationframe.js: -------------------------------------------------------------------------------- 1 | (function (window, undefined) { 2 | 3 | var freeExports = typeof exports == 'object' && exports, 4 | freeModule = typeof module == 'object' && module && module.exports == freeExports && module, 5 | freeGlobal = typeof global == 'object' && global; 6 | if (freeGlobal.global === freeGlobal) { 7 | window = freeGlobal; 8 | } 9 | 10 | var defaultNow = (function () { return !!Date.now ? Date.now : function () { return +new Date; }; }()); 11 | 12 | // Aliases 13 | var Rx = window.Rx, 14 | Scheduler = Rx.Scheduler, 15 | SingleAssignmentDisposable = Rx.SingleAssignmentDisposable, 16 | CompositeDisposable = Rx.CompositeDisposable, 17 | disposableCreate = Rx.Disposable.create; 18 | 19 | // Get the right animation frame method 20 | var requestAnimFrame, cancelAnimFrame; 21 | if (window.requestAnimationFrame) { 22 | requestAnimFrame = window.requestAnimationFrame; 23 | cancelAnimFrame = window.cancelAnimationFrame; 24 | } else if (window.mozRequestAnimationFrame) { 25 | requestAnimFrame = window.mozRequestAnimationFrame; 26 | cancelAnimFrame = window.mozCancelAnimationFrame; 27 | } else if (window.webkitRequestAnimationFrame) { 28 | requestAnimFrame = window.webkitRequestAnimationFrame; 29 | cancelAnimFrame = window.webkitCancelAnimationFrame; 30 | } else if (window.msRequestAnimationFrame) { 31 | requestAnimFrame = window.msRequestAnimationFrame; 32 | cancelAnimFrame = window.msCancelAnimationFrame; 33 | } else if (window.oRequestAnimationFrame) { 34 | requestAnimFrame = window.oRequestAnimationFrame; 35 | cancelAnimFrame = window.oCancelAnimationFrame; 36 | } else { 37 | requestAnimFrame = function(cb) { window.setTimeout(cb, 1000 / 60); }; 38 | cancelAnimFrame = window.clearTimeout; 39 | } 40 | 41 | /** 42 | * Gets a scheduler that schedules schedules work on the requestAnimationFrame for immediate actions. 43 | */ 44 | Rx.Scheduler.requestAnimationFrame = (function () { 45 | 46 | function scheduleNow(state, action) { 47 | var scheduler = this, 48 | disposable = new SingleAssignmentDisposable(); 49 | var id = requestAnimFrame(function () { 50 | if (!disposable.isDisposed) { 51 | disposable.setDisposable(action(scheduler, state)); 52 | } 53 | }); 54 | return new CompositeDisposable(disposable, disposableCreate(function () { 55 | cancelAnimFrame(id); 56 | })); 57 | } 58 | 59 | function scheduleRelative(state, dueTime, action) { 60 | var scheduler = this, 61 | dt = Scheduler.normalize(dueTime); 62 | if (dt === 0) { 63 | return scheduler.scheduleWithState(state, action); 64 | } 65 | 66 | var disposable = new SingleAssignmentDisposable(), 67 | id; 68 | var scheduleFunc = function () { 69 | if (id) { cancelAnimFrame(id); } 70 | if (dt - scheduler.now() <= 0) { 71 | if (!disposable.isDisposed) { 72 | disposable.setDisposable(action(scheduler, state)); 73 | } 74 | } else { 75 | id = requestAnimFrame(scheduleFunc); 76 | } 77 | }; 78 | 79 | id = requestAnimFrame(scheduleFunc); 80 | 81 | return new CompositeDisposable(disposable, disposableCreate(function () { 82 | cancelAnimFrame(id); 83 | })); 84 | } 85 | 86 | function scheduleAbsolute(state, dueTime, action) { 87 | return this.scheduleWithRelativeAndState(state, dueTime - this.now(), action); 88 | } 89 | 90 | return new Scheduler(defaultNow, scheduleNow, scheduleRelative, scheduleAbsolute); 91 | 92 | }()); 93 | 94 | }(this)); -------------------------------------------------------------------------------- /lessons/schedulers/start.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Rx for JavaScript Rocks! 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | -------------------------------------------------------------------------------- /lessons/schedulers/start.js: -------------------------------------------------------------------------------- 1 | (function (window, undefined) { 2 | 3 | var freeExports = typeof exports == 'object' && exports, 4 | freeModule = typeof module == 'object' && module && module.exports == freeExports && module, 5 | freeGlobal = typeof global == 'object' && global; 6 | if (freeGlobal.global === freeGlobal) { 7 | window = freeGlobal; 8 | } 9 | 10 | var canvas, context, subscription; 11 | 12 | function init() { 13 | 14 | var host = document.body; 15 | 16 | canvas = document.createElement( 'canvas' ); 17 | 18 | // ie IE < 9 19 | if (typeof G_vmlCanvasManager !== 'undefined') { 20 | G_vmlCanvasManager.initElement(canvas); 21 | } 22 | 23 | canvas.width = 512; 24 | canvas.height = 512; 25 | 26 | context = canvas.getContext( '2d' ); 27 | 28 | host.appendChild( canvas ); 29 | } 30 | 31 | function createAnimation(scheduler) { 32 | scheduler || (scheduler = Rx.Scheduler.timeout); 33 | 34 | return Rx.Observable.generate( 35 | 0, 36 | function (x) { return true; }, 37 | function (x) { return x + 1; }, 38 | function (x) { return x; }, 39 | scheduler 40 | ) 41 | .timestamp(); 42 | } 43 | 44 | function draw(ts) { 45 | 46 | var time = ts.timestamp * 0.002; 47 | var x = Math.sin( time ) * 192 + 256; 48 | var y = Math.cos( time * 0.9 ) * 192 + 256; 49 | 50 | context.fillStyle = ts.value % 2 === 0 ? 'rgb(200,200,20)' : 'rgb(20,20,200)'; 51 | context.beginPath(); 52 | context.arc( x, y, 10, 0, Math.PI * 2, true ); 53 | context.closePath(); 54 | context.fill(); 55 | } 56 | 57 | function main () { 58 | init(); 59 | 60 | subscription = createAnimation().subscribe(draw); 61 | } 62 | 63 | // Exports 64 | window.main = main; 65 | window.createAnimation = createAnimation; 66 | 67 | }(this)); 68 | -------------------------------------------------------------------------------- /lessons/testing/tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | QUnit Testing Example 5 | 6 | 7 | 8 |
                                                  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /lessons/testing/tests.js: -------------------------------------------------------------------------------- 1 | (function (global) { 2 | 3 | var root = global.Rx, 4 | TestScheduler = root.TestScheduler, 5 | Observable = root.Observable, 6 | 7 | // Assertions 8 | CollectionAssert = root.CollectionAssert, 9 | 10 | // Shortcuts for onNext/onError/onCompleted 11 | onNext = root.ReactiveTest.onNext, 12 | onError = root.ReactiveTest.onError, 13 | onCompleted = root.ReactiveTest.onCompleted, 14 | subscribe = root.ReactiveTest.subscribe; 15 | 16 | /* Tests returnValue basic behavior */ 17 | test('returnValue Basic', function () { 18 | 19 | var scheduler = new TestScheduler(); 20 | 21 | // Returns 42 at one tick after subscribe (200) 22 | var results = scheduler.startWithCreate(function () { 23 | return Observable.returnValue(42, scheduler); 24 | }); 25 | 26 | // Expect a single onNext with 42 and an oncompleted one tick from subscribe 27 | var expectedMessages = [ 28 | onNext(201, 42), 29 | onCompleted(201) 30 | ]; 31 | 32 | CollectionAssert.assertEqual(expectedMessages, results.messages); 33 | }); 34 | 35 | /* Tests select method basic behavior */ 36 | test('select Basic', function () { 37 | 38 | var scheduler = new TestScheduler(); 39 | 40 | // Creates a hot observable with two messages after subscribe 41 | var xs = scheduler.createHotObservable( 42 | onNext(150, 1), 43 | onNext(210, 2), 44 | onCompleted(220) 45 | ); 46 | 47 | // Project the hot observable via select without index 48 | var results = scheduler.startWithCreate(function () { 49 | return xs.select(function (x) { return x + x; }); 50 | }); 51 | 52 | // Should get one onNext with 4 and a completed 53 | var expectedMessages = [ 54 | onNext(210, 4), 55 | onCompleted(220) 56 | ]; 57 | 58 | CollectionAssert.assertEqual(expectedMessages, results.messages); 59 | 60 | // Should subscribe at 200 and unsubscribe at 220 at last message 61 | var expectedSubscriptions = [ 62 | subscribe(200, 220) 63 | ]; 64 | 65 | CollectionAssert.assertEqual(expectedSubscriptions, xs.subscriptions); 66 | }); 67 | 68 | /* Tests select method basic behavior */ 69 | test('select Multiple', function () { 70 | 71 | var scheduler = new TestScheduler(); 72 | 73 | // Project forward two onNext messages after subscribe and one completed at 220 74 | var xs = scheduler.createHotObservable( 75 | onNext(150, 1), 76 | onNext(210, 2), 77 | onNext(215, 3), 78 | onCompleted(220) 79 | ); 80 | 81 | // Project the hot observable via select without index 82 | var results = scheduler.startWithCreate(function () { 83 | return xs.select(function (x) { return x + x; }); 84 | }); 85 | 86 | // Should get one at 210 for 4 and one at 215 for 6 until completed at 220 87 | var expectedMessages = [ 88 | onNext(210, 4), 89 | onNext(215, 6), 90 | onCompleted(220) 91 | ]; 92 | 93 | CollectionAssert.assertEqual(expectedMessages, results.messages); 94 | 95 | // Should subscribe at 200 and unsubscribe at 220 at last message 96 | var expectedSubscriptions = [ 97 | subscribe(200, 220) 98 | ]; 99 | 100 | CollectionAssert.assertEqual(expectedSubscriptions, xs.subscriptions); 101 | }); 102 | 103 | /* Tests select method empty behavior */ 104 | test('select Empty', function () { 105 | 106 | var scheduler = new TestScheduler(); 107 | 108 | // Project forward one onNext after subscribe and one completed at 220 109 | var xs = scheduler.createHotObservable( 110 | onNext(150, 1), 111 | onCompleted(220) 112 | ); 113 | 114 | // Project the hot observable via select without index 115 | var results = scheduler.startWithCreate(function () { 116 | return xs.select(function (x) { return x + x; }); 117 | }); 118 | 119 | var expectedMessages = [ 120 | onCompleted(220) 121 | ]; 122 | 123 | CollectionAssert.assertEqual(expectedMessages, results.messages); 124 | 125 | // Should subscribe at 200 and unsubscribe at 220 at last message 126 | var expectedSubscriptions = [ 127 | subscribe(200, 220) 128 | ]; 129 | 130 | CollectionAssert.assertEqual(expectedSubscriptions, xs.subscriptions); 131 | }); 132 | 133 | /* Tests select method never firing behavior */ 134 | test('select Never', function () { 135 | 136 | var scheduler = new TestScheduler(); 137 | 138 | // Project no messages after subscribe at 200 139 | var xs = scheduler.createHotObservable( 140 | onNext(150, 1) 141 | ); 142 | 143 | // Project the hot observable via select without index 144 | var results = scheduler.startWithCreate(function () { 145 | return xs.select(function (x) { return x + x; }); 146 | }); 147 | 148 | // Should expect no messages 149 | var expectedMessages = [ 150 | ]; 151 | 152 | CollectionAssert.assertEqual(expectedMessages, results.messages); 153 | 154 | // Should subscribe at 200 and unsubscribe at 1000 (infinity) 155 | var expectedSubscriptions = [ 156 | subscribe(200, 1000) 157 | ]; 158 | 159 | CollectionAssert.assertEqual(expectedSubscriptions, xs.subscriptions); 160 | }); 161 | 162 | /* Tests select method where the observable throws an exception */ 163 | test('select Throws', function () { 164 | var error = new Error('woops'); 165 | var scheduler = new TestScheduler(); 166 | 167 | // Project forward one onError after subscribe 168 | var xs = scheduler.createHotObservable( 169 | onNext(150, 1), 170 | onError(210, error) 171 | ); 172 | 173 | var results = scheduler.startWithCreate(function () { 174 | return xs.select(function (x) { return x + x; }); 175 | }); 176 | 177 | // Should expect only one message with an error at 210 178 | var expectedMessages = [ 179 | onError(210, error) 180 | ]; 181 | 182 | CollectionAssert.assertEqual(expectedMessages, results.messages); 183 | 184 | // Should subscribe at 200 and unsubscribe at 210 at point of error 185 | var expectedSubscriptions = [ 186 | subscribe(200, 210) 187 | ]; 188 | 189 | CollectionAssert.assertEqual(expectedSubscriptions, xs.subscriptions); 190 | }); 191 | 192 | /* Tests select method where the observable throws an exception */ 193 | test('select Selector Throws', function () { 194 | var error = new Error('woops'); 195 | var scheduler = new TestScheduler(); 196 | 197 | // Project forward one onNext after subscribe and one completed at 220 198 | var xs = scheduler.createHotObservable( 199 | onNext(150, 1), 200 | onNext(210, 2) 201 | ); 202 | 203 | var results = scheduler.startWithCreate(function () { 204 | return xs.select(function (x) { throw error; }); 205 | }); 206 | 207 | // Should expect only one message with an error at 210 208 | var expectedMessages = [ 209 | onError(210, error) 210 | ]; 211 | 212 | CollectionAssert.assertEqual(expectedMessages, results.messages); 213 | 214 | // Should subscribe at 200 and unsubscribe at 210 at point of error 215 | var expectedSubscriptions = [ 216 | subscribe(200, 210) 217 | ]; 218 | 219 | CollectionAssert.assertEqual(expectedSubscriptions, xs.subscriptions); 220 | }); 221 | 222 | /* Tests select method basic behavior */ 223 | test('select With Index Basic', function () { 224 | 225 | var scheduler = new TestScheduler(); 226 | 227 | // Project forward one onNext after subscribe and one completed at 220 228 | var xs = scheduler.createHotObservable( 229 | onNext(150, 1), 230 | onNext(210, 2), 231 | onCompleted(220) 232 | ); 233 | 234 | var results = scheduler.startWithCreate(function () { 235 | return xs.select(function (x, i) { return (x + x) * i; }); 236 | }); 237 | 238 | // Should expect one message with 0 and then complettion 239 | var expectedMessages = [ 240 | onNext(210, 0), 241 | onCompleted(220) 242 | ]; 243 | 244 | CollectionAssert.assertEqual(expectedMessages, results.messages); 245 | 246 | // Should subscribe at 200 and unsubscribe at 220 at last message 247 | var expectedSubscriptions = [ 248 | subscribe(200, 220) 249 | ]; 250 | 251 | CollectionAssert.assertEqual(expectedSubscriptions, xs.subscriptions); 252 | }); 253 | 254 | }(window)); -------------------------------------------------------------------------------- /lessons/testing/vendor/customassertions.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | 3 | var root = window.QUnit; 4 | 5 | /* Creates a message based upon actual/expected */ 6 | function createMessage(actual, expected) { 7 | return 'Expected: [' + expected.toString() + ']\r\nActual: [' + actual.toString() + ']'; 8 | } 9 | 10 | root.collectionAssert = { 11 | /* Assertion for collections of notification messages */ 12 | assertEqual: function (expected, actual, comparer, message) { 13 | comparer || (comparer = Rx.Internals.isEqual); 14 | var isOk = true, i, len; 15 | 16 | if (expected.length !== actual.length) { 17 | .rootok(false, 'Not equal length. Expected: ' + expected.length + ' Actual: ' + actual.length); 18 | return; 19 | } 20 | 21 | for(i = 0, len = expected.length; i < len; i++) { 22 | isOk = comparer(expected[i], actual[i]); 23 | if (!isOk) { 24 | break; 25 | } 26 | } 27 | 28 | root.ok(isOk, message || createMessage(expected, actual)); 29 | } 30 | }; 31 | 32 | }(this)); -------------------------------------------------------------------------------- /lessons/testing/vendor/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.10.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://qunitjs.com 5 | * 6 | * Copyright 2012 jQuery Foundation and other contributors 7 | * Released under the MIT license. 8 | * http://jquery.org/license 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 5px 5px 0 0; 42 | -moz-border-radius: 5px 5px 0 0; 43 | -webkit-border-top-right-radius: 5px; 44 | -webkit-border-top-left-radius: 5px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-testrunner-toolbar label { 58 | display: inline-block; 59 | padding: 0 .5em 0 .1em; 60 | } 61 | 62 | #qunit-banner { 63 | height: 5px; 64 | } 65 | 66 | #qunit-testrunner-toolbar { 67 | padding: 0.5em 0 0.5em 2em; 68 | color: #5E740B; 69 | background-color: #eee; 70 | overflow: hidden; 71 | } 72 | 73 | #qunit-userAgent { 74 | padding: 0.5em 0 0.5em 2.5em; 75 | background-color: #2b81af; 76 | color: #fff; 77 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 78 | } 79 | 80 | #qunit-modulefilter-container { 81 | float: right; 82 | } 83 | 84 | /** Tests: Pass/Fail */ 85 | 86 | #qunit-tests { 87 | list-style-position: inside; 88 | } 89 | 90 | #qunit-tests li { 91 | padding: 0.4em 0.5em 0.4em 2.5em; 92 | border-bottom: 1px solid #fff; 93 | list-style-position: inside; 94 | } 95 | 96 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 97 | display: none; 98 | } 99 | 100 | #qunit-tests li strong { 101 | cursor: pointer; 102 | } 103 | 104 | #qunit-tests li a { 105 | padding: 0.5em; 106 | color: #c2ccd1; 107 | text-decoration: none; 108 | } 109 | #qunit-tests li a:hover, 110 | #qunit-tests li a:focus { 111 | color: #000; 112 | } 113 | 114 | #qunit-tests ol { 115 | margin-top: 0.5em; 116 | padding: 0.5em; 117 | 118 | background-color: #fff; 119 | 120 | border-radius: 5px; 121 | -moz-border-radius: 5px; 122 | -webkit-border-radius: 5px; 123 | } 124 | 125 | #qunit-tests table { 126 | border-collapse: collapse; 127 | margin-top: .2em; 128 | } 129 | 130 | #qunit-tests th { 131 | text-align: right; 132 | vertical-align: top; 133 | padding: 0 .5em 0 0; 134 | } 135 | 136 | #qunit-tests td { 137 | vertical-align: top; 138 | } 139 | 140 | #qunit-tests pre { 141 | margin: 0; 142 | white-space: pre-wrap; 143 | word-wrap: break-word; 144 | } 145 | 146 | #qunit-tests del { 147 | background-color: #e0f2be; 148 | color: #374e0c; 149 | text-decoration: none; 150 | } 151 | 152 | #qunit-tests ins { 153 | background-color: #ffcaca; 154 | color: #500; 155 | text-decoration: none; 156 | } 157 | 158 | /*** Test Counts */ 159 | 160 | #qunit-tests b.counts { color: black; } 161 | #qunit-tests b.passed { color: #5E740B; } 162 | #qunit-tests b.failed { color: #710909; } 163 | 164 | #qunit-tests li li { 165 | padding: 5px; 166 | background-color: #fff; 167 | border-bottom: none; 168 | list-style-position: inside; 169 | } 170 | 171 | /*** Passing Styles */ 172 | 173 | #qunit-tests li li.pass { 174 | color: #3c510c; 175 | background-color: #fff; 176 | border-left: 10px solid #C6E746; 177 | } 178 | 179 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 180 | #qunit-tests .pass .test-name { color: #366097; } 181 | 182 | #qunit-tests .pass .test-actual, 183 | #qunit-tests .pass .test-expected { color: #999999; } 184 | 185 | #qunit-banner.qunit-pass { background-color: #C6E746; } 186 | 187 | /*** Failing Styles */ 188 | 189 | #qunit-tests li li.fail { 190 | color: #710909; 191 | background-color: #fff; 192 | border-left: 10px solid #EE5757; 193 | white-space: pre; 194 | } 195 | 196 | #qunit-tests > li:last-child { 197 | border-radius: 0 0 5px 5px; 198 | -moz-border-radius: 0 0 5px 5px; 199 | -webkit-border-bottom-right-radius: 5px; 200 | -webkit-border-bottom-left-radius: 5px; 201 | } 202 | 203 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 204 | #qunit-tests .fail .test-name, 205 | #qunit-tests .fail .module-name { color: #000000; } 206 | 207 | #qunit-tests .fail .test-actual { color: #EE5757; } 208 | #qunit-tests .fail .test-expected { color: green; } 209 | 210 | #qunit-banner.qunit-fail { background-color: #EE5757; } 211 | 212 | 213 | /** Result */ 214 | 215 | #qunit-testresult { 216 | padding: 0.5em 0.5em 0.5em 2.5em; 217 | 218 | color: #2b81af; 219 | background-color: #D2E0E6; 220 | 221 | border-bottom: 1px solid white; 222 | } 223 | #qunit-testresult .module-name { 224 | font-weight: bold; 225 | } 226 | 227 | /** Fixture */ 228 | 229 | #qunit-fixture { 230 | position: absolute; 231 | top: -10000px; 232 | left: -10000px; 233 | width: 1000px; 234 | height: 1000px; 235 | } 236 | --------------------------------------------------------------------------------