├── .gitignore ├── .nodemonignore ├── Dockerfile ├── README.md ├── deploy └── docker-compose.yaml ├── md ├── docs │ ├── derby-0.10 │ │ ├── apps │ │ │ ├── index.html │ │ │ ├── overview.md │ │ │ └── routes.md │ │ ├── components │ │ │ ├── component-class.md │ │ │ ├── events.md │ │ │ ├── index.html │ │ │ ├── lifecycle.md │ │ │ ├── overview.md │ │ │ ├── scope.md │ │ │ └── view-partials.md │ │ ├── index.html │ │ ├── models │ │ │ ├── backends.md │ │ │ ├── data-loading-contexts.md │ │ │ ├── events.md │ │ │ ├── filters-and-sorts.md │ │ │ ├── getters.md │ │ │ ├── index.html │ │ │ ├── overview.md │ │ │ ├── paths.md │ │ │ ├── queries.md │ │ │ ├── reactive-functions.md │ │ │ ├── references.md │ │ │ └── setters.md │ │ ├── overview.md │ │ └── views │ │ │ ├── index.html │ │ │ ├── namespaces-and-files.md │ │ │ ├── overview.md │ │ │ └── template-syntax │ │ │ ├── blocks.md │ │ │ ├── escaping.md │ │ │ ├── functions-and-events.md │ │ │ ├── index.html │ │ │ ├── literals.md │ │ │ ├── operators.md │ │ │ ├── overview.md │ │ │ ├── paths.md │ │ │ └── view-attributes.md │ └── index.html ├── faq.md ├── faq │ ├── faq_en.md │ └── faq_ru.md ├── index.html ├── resources.md └── started.md ├── package-lock.json ├── package.json ├── public ├── .gitkeep ├── css │ ├── bootstrap-theme.min.css │ ├── bootstrap.min.css │ ├── docs.min.css │ ├── font-awesome.min.css │ ├── github.min.css │ └── rainbow.css ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff ├── images │ ├── arrows.png │ ├── book.png │ ├── browser.png │ ├── comments.png │ ├── derby-medium-cream.png │ ├── derby-medium-transparent.png │ ├── derby.png │ ├── derbyparty.jpg │ ├── docs │ │ └── charts-debug.png │ ├── favicon.ico │ ├── flag.png │ ├── github.png │ ├── hourse-logo.png │ ├── html5logos.png │ ├── logo.png │ ├── love.png │ ├── mail.png │ ├── nodejs.png │ ├── octocat.png │ ├── octocat30.png │ ├── responsive.png │ ├── ticket.png │ └── user.png ├── js │ ├── app.js │ ├── bootstrap.min.js │ └── jquery.min.js └── robots.txt ├── server.js ├── src └── server │ ├── derbyLanguage.js │ ├── error.js │ ├── index.js │ ├── markdown.js │ ├── outline.js │ └── site.js ├── styles ├── app │ ├── colors.styl │ ├── docs.styl │ ├── home.styl │ ├── index.styl │ └── syntax.styl ├── error │ ├── index.styl │ └── reset.styl └── libs │ ├── bootstrap.min.css │ ├── docs.min.css │ └── font-awesome.min.css └── views ├── app ├── home.html └── index.html └── error ├── 403.html ├── 404.html ├── 500.html └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | node_modules 4 | views/gen 5 | npm-debug.log 6 | /.idea 7 | public/derby 8 | fabfile.pyc 9 | -------------------------------------------------------------------------------- /.nodemonignore: -------------------------------------------------------------------------------- 1 | public/derby -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # BUILD-USING: docker build -t derbyjs/derby-site . 2 | # RUN-USING: docker run -p 4000:4000 --name derby-site --rm derbyjs/derby-site 3 | 4 | # specify base docker image 5 | FROM node:20 6 | 7 | # copy over dependencies 8 | WORKDIR /var 9 | RUN mkdir derby-site 10 | 11 | ADD package.json /var/derby-site/ 12 | ADD server.js /var/derby-site/ 13 | 14 | ADD md /var/derby-site/md 15 | ADD public /var/derby-site/public 16 | ADD src /var/derby-site/src 17 | ADD styles /var/derby-site/styles 18 | ADD views /var/derby-site/views 19 | 20 | # npm install all the things 21 | WORKDIR /var/derby-site 22 | RUN npm_config_spin=false npm_config_loglevel=warn npm install --production 23 | 24 | # expose any ports we need 25 | EXPOSE 4000 26 | ENV PORT 4000 27 | # the command that gets run inside the docker container 28 | CMD ["/usr/local/bin/node", "/var/derby-site/server.js"] 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | derby-site 2 | ============= 3 | The [derbyjs.com](//derbyjs.com/) site, built with Derby. 4 | 5 | The docs in this repo are deprecated. Up-to-date docs are now maintained in the [main derby repo's docs directory](https://github.com/derbyjs/derby/tree/master/docs) and are automatically deployed to GitHub Pages at [derbyjs.github.io/derby/](https://derbyjs.github.io/derby/). 6 | 7 | Installation 8 | ------------ 9 | 10 | Clone this repository and install the dependencies: 11 | 12 | ```shell 13 | npm install 14 | ``` 15 | 16 | Development 17 | ----------- 18 | 19 | To run the site locally: 20 | 21 | ```shell 22 | npm start 23 | ``` 24 | 25 | To run the entire stack locally, you can use `docker-compose`. To do this, 26 | simply run: 27 | 28 | ```shell 29 | npm run compose-up 30 | ``` 31 | 32 | Similarly, `npm run compose-stop` will stop all containers and 33 | `npm run compose-down` will stop and remove all containers and networks created. 34 | 35 | You can also use Docker Compose directly by running the following command: 36 | 37 | ```shell 38 | SWARM_MODE=false docker-compose -f ./deploy/docker-compose.yaml up 39 | ``` 40 | 41 | To change the underlying versions of `derby-site` or `derby-examples`, simply 42 | adjust the tags for the `image`. 43 | 44 | Production 45 | ---------- 46 | 47 | The Derby site can operate using the Docker Compose or, more ideally, Docker 48 | Swarm. The use Swarm, you must first initialize a swarm cluster. To do this, 49 | simply run `docker swarm init`. If you are prompted to include the 50 | `--advertise-addr` parameter, make sure this matches the instances **local** IP 51 | address, **not** the public address. Once you have done this, you can run the 52 | following command to create the stack from the `./deploy` directory: 53 | 54 | ```shell 55 | npm run deploy 56 | ``` 57 | 58 | Alternatively, you can run this directly using the Docker CLI with the following 59 | command: 60 | 61 | ```shell 62 | SWARM_MODE=true docker stack deploy --compose-file docker-compose.yaml derbyjs 63 | ``` 64 | 65 | This will create all necessary resources. If you are making changes to the 66 | configuration or want to update to a new version, you can simply edit the 67 | `docker-compose.yaml` file and run the command listed above again. This will 68 | initiate a rolling update. 69 | 70 | Note that the only container utilizing [rolling updates](https://docs.docker.com/engine/swarm/swarm-tutorial/rolling-update/) is the `derby-site` 71 | container. 72 | 73 | 74 | Build and Deploy 75 | ---------------- 76 | 77 | To build the DerbyJS Site, clone this repo locally as described above. 78 | 79 | Build a docker image with a tag containing today's date, push to Docker Hub, and cleanup the local copy of the image: 80 | ``` 81 | docker build --platform linux/amd64 . --tag "derbyjs/derby-site:$(date +%Y-%m-%d)" 82 | docker push "derbyjs/derby-site:$(date +%Y-%m-%d)" 83 | docker rmi "derbyjs/derby-site:$(date +%Y-%m-%d)" 84 | ``` 85 | 86 | 87 | Next, update the deployed tag specified in [/deploy/docker.compose.yaml](https://github.com/derbyjs/derby-site/blob/master/deploy/docker-compose.yaml). The tag is defined in `services: derbysite: image: derbyjs/derby-site:YYYY-MM-DD`, and it should be updated to the current date. Commit and push your changes to GitHub. 88 | 89 | Finally, SSH to the derby-site server: 90 | ``` 91 | ssh derbyjs-01.droplet.derbyjs.com 92 | ``` 93 | and run the command to update: 94 | ``` 95 | cd ~/derby-site/deploy/ && git pull && npm run deploy 96 | ``` 97 | -------------------------------------------------------------------------------- /deploy/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | mongo: 5 | image: mongo:3.6 6 | ports: 7 | - "27017:27017" 8 | networks: 9 | - derbyjs_overlay 10 | 11 | redis: 12 | image: redis:3 13 | ports: 14 | - "6379:6379" 15 | networks: 16 | - derbyjs_overlay 17 | 18 | derby_site: 19 | image: derbyjs/derby-site:2024-05-31 20 | deploy: 21 | replicas: 2 22 | update_config: 23 | parallelism: 1 24 | delay: 10s 25 | labels: &derby_site_labels 26 | traefik.enable: "true" 27 | traefik.docker.network: derbyjs_derbyjs_overlay 28 | traefik.http.routers.derby-site.rule: Host(`derbyjs.com`) || Host(`www.derbyjs.com`) || Path(`/robots.txt`) 29 | traefik.http.services.derby-site.loadbalancer.server.port: 4000 30 | traefik.priority: 100 31 | ports: 32 | - "4000:4000" 33 | labels: *derby_site_labels 34 | networks: 35 | - derbyjs_overlay 36 | 37 | derby_examples: 38 | image: derbyjs/derby-examples:2024-05-20 39 | deploy: 40 | replicas: 1 41 | update_config: 42 | parallelism: 1 43 | delay: 10s 44 | labels: &derby_examples_labels 45 | traefik.enable: "true" 46 | traefik.docker.network: derbyjs_derbyjs_overlay 47 | 48 | traefik.http.routers.charts.rule: Host(`charts.derbyjs.com`) 49 | traefik.http.routers.charts.service: charts 50 | traefik.http.services.charts.loadbalancer.server.port: 8001 51 | 52 | traefik.http.routers.chat.rule: Host(`chat.derbyjs.com`) 53 | traefik.http.routers.chat.service: chat 54 | traefik.http.services.chat.loadbalancer.server.port: 8002 55 | 56 | traefik.http.routers.codemirror.rule: Host(`codemirror.derbyjs.com`) 57 | traefik.http.routers.codemirror.service: codemirror 58 | traefik.http.services.codemirror.loadbalancer.server.port: 8003 59 | 60 | traefik.http.routers.directory.rule: Host(`directory.derbyjs.com`) 61 | traefik.http.routers.directory.service: directory 62 | traefik.http.services.directory.loadbalancer.server.port: 8004 63 | 64 | traefik.http.routers.hello.rule: Host(`hello.derbyjs.com`) 65 | traefik.http.routers.hello.service: hello 66 | traefik.http.services.hello.loadbalancer.server.port: 8005 67 | 68 | traefik.http.routers.sink.rule: Host(`sink.derbyjs.com`) 69 | traefik.http.routers.sink.service: sink 70 | traefik.http.services.sink.loadbalancer.server.port: 8006 71 | 72 | traefik.http.routers.todos.rule: Host(`todos.derbyjs.com`) 73 | traefik.http.routers.todos.service: todos 74 | traefik.http.services.todos.loadbalancer.server.port: 8007 75 | 76 | traefik.http.routers.widgets.rule: Host(`widgets.derbyjs.com`) 77 | traefik.http.routers.widgets.service: widgets 78 | traefik.http.services.widgets.loadbalancer.server.port: 8008 79 | 80 | traefik.http.routers.render.rule: Host(`render.derbyjs.com`) 81 | traefik.http.routers.render.service: render 82 | traefik.http.services.render.loadbalancer.server.port: 8009 83 | 84 | ports: 85 | - "8001:8001" 86 | - "8002:8002" 87 | - "8003:8003" 88 | - "8004:8004" 89 | - "8005:8005" 90 | - "8006:8006" 91 | - "8007:8007" 92 | - "8008:8008" 93 | - "8009:8009" 94 | depends_on: 95 | - mongo 96 | - redis 97 | environment: 98 | MONGO_HOST: mongo 99 | REDIS_HOST: redis 100 | labels: *derby_examples_labels 101 | networks: 102 | - derbyjs_overlay 103 | 104 | traefik: 105 | image: traefik:v2.11 106 | deploy: 107 | replicas: 2 108 | update_config: 109 | parallelism: 1 110 | delay: 10s 111 | volumes: 112 | - /var/run/docker.sock:/var/run/docker.sock 113 | ports: 114 | - "0.0.0.0:8080:80" 115 | command: 116 | - --api=true 117 | - --ping=true 118 | - --ping.entrypoint=http 119 | - --providers.docker 120 | - --providers.docker.exposedbydefault=false 121 | - --providers.docker.swarmmode=${SWARM_MODE} 122 | networks: 123 | - derbyjs_overlay 124 | 125 | networks: 126 | derbyjs_overlay: 127 | driver: overlay 128 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/apps/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/apps/overview.md: -------------------------------------------------------------------------------- 1 | # Derby Apps 2 | 3 | Derby projects support one or more single-page apps. 4 | Apps have a full MVC structure, including a model provided by 5 | [Racer](https://github.com/derbyjs/racer), a template and styles based view, and controller 6 | code with application logic and routes (which map URLs to actions). 7 | 8 | On the server, apps provide a router middleware for Express. One or more app 9 | routers as well as server only routes can be included in the same Express 10 | server. 11 | 12 | Derby packages up all of an app's templates, routes, and application code when 13 | rendering. Regardless of which app URL the browser requests initially, the app 14 | is able to render any other state within the same application client-side. If 15 | the app cannot handle a URL, it will fall through and request from the server. 16 | Errors thrown during route handling also cause requests to fall through to the 17 | server. 18 | 19 | Derby works great with only a single app, though developers may wish to create 20 | separate apps if only certain sets of pages are likely to be used together. For 21 | example, a project might have a separate desktop web app and mobile web app. Or 22 | a project might have an internal administration panel app and a public content 23 | app. 24 | 25 | 26 | ## Creating apps 27 | 28 | Apps are created in the file that defines the app's controller code. They are 29 | then associated with a server by requiring the app within the server file. 30 | 31 | > `app = derby.createApp ( name, fileName )` 32 | > 33 | > * `name`: the name of the app 34 | > * `fileName`: the name of the file, typically node's default __filename is used. 35 | > 36 | > * `app`: Returns an app object, typically exported as `module.exports = app` 37 | 38 | 39 | App names are used to automatically associate an app with template and styles files of the same 40 | name. 41 | 42 | The `createApp` method adds a number of methods to the app. On both the client 43 | and the server, these are `use`, `get`, `post`, `put`, `del`, 44 | and `ready`. On the server only, Derby also adds `router`, 45 | for use with Express. 46 | 47 | ## Connecting servers to apps 48 | 49 | Because Derby shares most code between server and client, Derby server files 50 | can be very minimal. 51 | 52 | The server includes an app with a standard Node.js require statement. It can 53 | then use the `app.router()` method to create a router middleware for Express 54 | that handles all of the app's routes. 55 | 56 | The server also needs to create a `store` object, which is what creates models, 57 | coordinates data syncing, and interfaces with databases. Stores are created via 58 | the `derby.createStore()` method. See [Backends](models/backends). 59 | 60 | > A typical setup can be seen in the [derby-starter](https://github.com/derbyjs/derby-starter/blob/master/lib/server.js) project, which is a node module for getting started with Derby. 61 | > 62 | > The [derby-examples](https://github.com/derbyjs/derby-examples) make use of derby-starter to setup their apps. -------------------------------------------------------------------------------- /md/docs/derby-0.10/apps/routes.md: -------------------------------------------------------------------------------- 1 | # Routes 2 | 3 | Routes map URL patterns to actions. Derby routes are powered by [Express](https://expressjs.com/). Within apps, routes are defined via the `get`, `post`, `put`, and `del` methods of the app created by `derby.createApp()`. 4 | 5 | > `app.get ( routePattern, callback(page, model, params, next) )` 6 | > 7 | > `app.post ( routePattern, callback(page, model, params, next) )` 8 | > 9 | > `app.put ( routePattern, callback(page, model, params, next) )` 10 | > 11 | > `app.del ( routePattern, callback(page, model, params, next) )` 12 | > 13 | > * `routePattern`: A string containing a literal URL, an Express route pattern, or a regular expression. See [Express's routing documentation](https://expressjs.com/guide/routing.html) for more info. Derby also supports transitional routes which can be passed in as an object with two string properties, `from` and `to`. 14 | > 15 | > * `callback`: Function invoked when a request for a URL matching the appropriate HTTP method and pattern is received. Note that this function is called both on the server and the client. 16 | > 17 | > * `page`: Object with the methods [`page.render()`](#pagerender) and `page.redirect()`. All app routes should call one of these two methods or pass control by calling `next()`. 18 | > 19 | > * `model`: Derby model object 20 | > 21 | > * `params`: An object containing the matching URL parameters. The `url`, `query`, and `body` properties typically available on `req` are also added to this object. 22 | > 23 | > * `next`: A function that can be called to pass control to the next matching route. If this is called on the client, control will be passed to the next route defined in the app. If no other routes in the same app match, it will fall through to a server request. 24 | 25 | Express is used directly on the server. On the client, Derby includes Express's route matching module. When a link is clicked or a form is submitted, Derby first tries to render the new URL on the client. AJAX requests will still go directly to the server. 26 | 27 | Derby can also capture form submissions client-side. It provides support for `post`, `put`, and `del` HTTP methods using the same hidden form field [override approach](https://expressjs.com/guide.html#http-methods) as Express. 28 | 29 | ## Page 30 | 31 | Unlike Express, which provides direct access to the `req` and `res` objects created by Node HTTP servers, Derby returns a `page` object. This provide the same interface on the client and the server, so that route handlers may be executed in both environments. 32 | 33 | > `page.render ( viewName )` 34 | > 35 | > * `viewName`: The name of the view to render, see [Namespaces and files](../views/namespaces-and-files) for more details. 36 | > 37 | > 38 | > `page.renderStatic ( statusCode, content )` 39 | > 40 | > * `statusCode`: The HTTP status code to return. 41 | > 42 | > * `content`: A string of HTML to render 43 | > 44 | > `page.redirect ( url, [status] )` 45 | > 46 | > * `url`: Destination of redirect. [Like Express][expressRedirect], may also be the string 'home' (which redirects to '/') or 'back' (which goes back to the previous URL). 47 | > 48 | > * `status`: *(optional)* Number specifying HTTP status code. Defaults to 302 on the server. Has no effect on the client. 49 | 50 | [expressRedirect]: https://expressjs.com/guide.html#res.redirect() 51 | 52 | 53 | ### Middleware 54 | 55 | It is possible to directly use [express middleware](https://expressjs.com/guide/using-middleware.html) and get access to a [Racer model](../models#methods). 56 | 57 | 58 | ## History 59 | 60 | For the most part, updating the URL client-side should be done with normal HTML links. The default action of requesting a new page from the server is canceled automatically if the app has a route that matches the new URL. 61 | 62 | To update the URL after an action other than clicking a link, scripts can call methods on `app.history`. For example, an app might update the URL as the user scrolls and the page loads more content from a paginated list. 63 | 64 | > `app.history.push ( url, [render], [state], [e] )` 65 | > 66 | > `app.history.replace ( url, [render], [state], [e] )` 67 | > 68 | > * `url`: New URL to set for the current window 69 | > 70 | > * `render`: *(optional)* Re-render the page after updating the URL if true. Defaults to true 71 | > 72 | > * `state`: *(optional)* A state object to pass to the `window.history.pushState` or `window.history.replaceState` method. `$render` and `$method` properties are added to this object for internal use when handling `popstate` events 73 | > 74 | > * `e`: *(optional)* An event object whose `stopPropogation` method will be called if the URL can be rendered client-side 75 | 76 | Derby's `history.push` and `history.replace` methods will update the URL via `window.history.pushState` or `window.history.replaceState`, respectively. They will fall back to setting `window.location` and server-rendering the new URL if a browser does not support these methods. The `push` method is used to update the URL and create a new entry in the browser's back/forward history. The `replace` method is used to only update the URL without creating an entry in the back/forward history. 77 | 78 | > `app.history.refresh ( )` 79 | > 80 | > Re-render the current URL client-side 81 | 82 | For convenience, the navigational methods of [`window.history`](https://developer.mozilla.org/en/DOM/window.history) can also be called on `app.history`. 83 | 84 | > `app.history.back ( )` 85 | > 86 | > * Call `window.history.back()`, which is equivalent to clicking the browser's back button 87 | 88 | > `view.history.forward ( )` 89 | > 90 | > * Call `window.history.forward()`, which is equivalent to clicking the browser's forward button 91 | 92 | > `view.history.go ( i )` 93 | > 94 | > * Call `window.history.go()` 95 | > 96 | > * `i`: An integer specifying the number of times to go back or forward. Navigates back if negative or forward if positive 97 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/components/component-class.md: -------------------------------------------------------------------------------- 1 | # Component class 2 | 3 | Derby provides a base class `Component`, from which all component classes inherit. When authoring a component, you can extend Derby's Component class with JavaScript, TypeScript, or CoffeeScript `extends` syntax. 4 | 5 | ```js 6 | const Component = require('derby').Component; 7 | class MyComponent extends Component { 8 | ... 9 | } 10 | app.component(MyComponent); 11 | ``` 12 | 13 | For convenience, if you register a class that does not inherit from `Component`, Derby will add `Component.prototype` to your class's prototype chain. In other words, Derby will make sure that your class inherits from `Component` at the time that you call `app.component()`. 14 | 15 | ```js 16 | class MyComponent { 17 | ... 18 | } 19 | app.component(MyComponent); 20 | ``` 21 | 22 | ## Component configuration 23 | 24 | Components are configured by defining the following static properties and methods: 25 | 26 | > `MyComponent.view = '/path/to/view'` The relative file path to a template file to load. If the view file is named *index.html* and in the same directory as the controller, `__dirname` can be used 27 | 28 | > `MyComponent.is = 'my-component'` The name to use for the component's view. Often this doesn't need to be specified, because it defaults to the basename of the file or directory. 29 | 30 | > `MyComponent.DataConstructor` Constructor function for setting default values in the component's model data. Properties will be overriden by view attributes. 31 | 32 | > `MyComponent.prototype.init = function(model)` Called immediately before the view is rendered. Data and reactive functions can be initialized on the component's scoped model. This method is invovked both on the server and on the client, so the DOM and browser-only methods may not be used within init(). 33 | 34 | > `MyComponent.prototype.create = function(model, dom)` Called in the browser when a component is loaded and inserted into the DOM. This method is never called on the server. DOM-related code and model event listeners should be placed in create(). 35 | 36 | 37 | ## Properties 38 | 39 | > `model`: The component's scoped model. 40 | 41 | > `dom`: An instance of Derby's wrapper around DOM methods. This should be used for adding and removing listeners to DOM events rather than native `addEventListener()`. This is important so that Derby can remove listeners when the component is destroyed. 42 | 43 | > `page`: Reference to the current page object, which is the top level controller. A new page object is created on navigation to a new URL. 44 | 45 | > `app`: Reference to the current app object. The app is persistent for the entire session. 46 | 47 | > `parent`: Reference to the containing controller. 48 | 49 | > `context`: The rendering context object. 50 | 51 | > `id`: The unique id assigned to the component. 52 | 53 | > `isDestroyed`: Initially set to `false`. Set to `true` when the component is fully destroyed. 54 | 55 | 56 | ## Methods 57 | 58 | ### Event emission 59 | 60 | Components are Node.js event emitters, so they inherit the `on`, `once`, `emit`, `removeListener`, etc. methods from [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). 61 | 62 | ### Cleanup 63 | 64 | > `component.destroy()` 65 | > 66 | > Derby calls this method when removing a component's marker comment from the DOM. `destroy()` emits the `'destroy'` event on the component. Listen for the destroy event in order to implement custom cleanup logic. This method should not be invoked manually. 67 | 68 | ```derby 69 | 70 | ``` 71 | 72 | ```js 73 | class MyComponent extends Component { 74 | create() { 75 | this.on('destroy', function() { 76 | // Custom cleanup logic 77 | }); 78 | } 79 | } 80 | ``` 81 | 82 | > `boundFn = component.bind(fn)` 83 | > * `fn` - _Function_ - A function to be invoked with the component as its `this` value. In addition, the function will no longer be invoked once the component is destroyed 84 | > * `boundFn` - _Function_ - Returns a bound function, similar to JavaScript's `Function.bind()`. This function is safer to use in asynchronous code, such as with setTimeout, requestAnimationFrame, or requests to the server, because it won't call back after the component is destroyed. Internally, references to `fn` and the component are removed on `'destroy'`, which allows them to be garbage collected even if a reference to `boundFn` is held. 85 | 86 | ```js 87 | class MyComponent extends Component { 88 | load() { 89 | this.set('loading', true); 90 | setTimeout(this.bind(function() { 91 | // This won't execute if the component has been destroyed 92 | this.set('loading', false); 93 | }), 200); 94 | } 95 | } 96 | ``` 97 | 98 | ### Throttling and debouncing 99 | 100 | Derby components have built-in support for common throttling and debouncing patterns. These methods are similar to those provided by general-purpose libraries like Lodash, but they also bind the `this` value to the component, provide added safety by not calling back after a component is destroyed, and release references to `fn` and the component on `'destroy'`, same as `component.bind(fn)`. 101 | 102 | > `throttledFn = component.throttle(fn, [delayArg = 0])` 103 | > 104 | > When passing in a numeric delay, calls the function at most once per that many milliseconds. Like Lodash, the function will be called on the leading and the trailing edge of the delay as appropriate. Unlike Lodash, calls are consistently called via setTimeout and are never synchronous. This should be used for reducing the frequency of ongoing updates, such as scroll events or other continuous streams of events. 105 | > 106 | > Additionally, implements an interface intended to be used with [window.requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame), process.nextTick, or window.setImmediate. If one of these is passed, it will be used to create a single async call following any number of synchronous calls. This mode is typically used to coalesce many synchronous events (such as multiple model events) into a single async event. 107 | > 108 | > Like `component.bind()`, will no longer call back once the component is destroyed, which avoids possible bugs and memory leaks. 109 | 110 | ```js 111 | class MyComponent extends Component { 112 | create() { 113 | // Call this.update() at most once every 75 milliseconds 114 | this.dom.on('scroll', window, this.throttle(this.update, 75)); 115 | } 116 | update() { 117 | // Update based on scroll location 118 | } 119 | } 120 | ``` 121 | 122 | ```js 123 | class MyComponent extends Component { 124 | create() { 125 | // Call this.update() at most once before each paint (typically 60 times / second) 126 | this.dom.on('scroll', window, this.throttle(this.update, window.requestAnimationFrame)); 127 | } 128 | update() { 129 | // Update based on scroll location 130 | } 131 | } 132 | ``` 133 | 134 | > `debouncedFn = component.debounce(fn, [delay = 0])` 135 | > 136 | > Suppresses calls until the function is no longer called for that many milliseconds. This should be used for delaying updates triggered by user input, such as window resizing, or typing text that has a live preview or client-side validation. This should not be used for inputs that trigger server requests, such as search autocomplete; use debounceAsync for those cases instead. 137 | > 138 | > Like `component.bind()`, will no longer call back once the component is destroyed, which avoids possible bugs and memory leaks. 139 | 140 | ```derby 141 | 142 | 143 | ``` 144 | 145 | ```js 146 | class MyComponent extends Component { 147 | create() { 148 | // Suppress calls until the user has stopped typing for 300 milliseconds 149 | this.dom.on('input', this.textInput, this.debounce(this.update, 300)); 150 | } 151 | update() { 152 | // Update based on current value 153 | } 154 | } 155 | ``` 156 | 157 | > `asyncDebouncedFn = component.debounceAsync(fn, [delay = 0])` 158 | > 159 | > Like debounce(), suppresses calls until the function is no longer called for that many milliseconds. In addition, suppresses calls while the callback function is running. In other words, the callback will not be called again until the supplied `done()` argument is called. When the debounced function is called while the callback is running, the callback will be called again immediately after `done()` is called. Thus, the callback will always receive the last value passed to the debounced function. 160 | > 161 | > This avoids the potential for multiple callbacks to execute in parallel and complete out of order. It also acts as an adaptive rate limiter. Use this method to debounce any field that triggers an async call as the user types. 162 | > 163 | > Like `component.bind()`, will no longer call back once the component is destroyed, which avoids possible bugs and memory leaks. 164 | 165 | ```derby 166 | 167 | 168 | ``` 169 | 170 | ```js 171 | class MyComponent extends Component { 172 | create() { 173 | // Suppress calls until the user has stopped typing for 300 milliseconds 174 | // and the async function has completed 175 | this.dom.on('input', this.textInput, this.debounceAsync(this.search, 300)); 176 | } 177 | search(done) { 178 | const query = this.model.get('value'); 179 | fetch('/api/search?q=' + query) 180 | .then(response => { 181 | this.model.set('response', response); 182 | }) 183 | .catch(err => console.error(err)) 184 | // No additional calls to search will happen until done() is called 185 | .finally(done); 186 | } 187 | } 188 | ``` 189 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/components/events.md: -------------------------------------------------------------------------------- 1 | # Events 2 | 3 | Functions defined on a property of a controller can be invoked from view expressions or in response to events. As a general pattern, view paths refer to the model when getting values and to the controller when calling functions. 4 | 5 | Functions are looked up on the current component's controller, the page, and the global, in that order. See the [view functions and events](../views/template-syntax/functions-and-events#controller-property-lookup) documentation for more detail. 6 | 7 | ## Lifecycle events 8 | 9 | Default events are triggered during the lifecycle of a component: 10 | 11 | * `init`: Emitted before the component's `init()` function is called. 12 | * `create`: Emitted before the component's `create()` function is called. 13 | * `destroy`: Emitted before the component's `destroy()` function is called. 14 | 15 | If the functions to be called aren't defined on the component, their respective events are still triggered unconditionally. 16 | 17 | ## Custom events 18 | 19 | Components support custom events. Dashes are transformed into camelCase. 20 | ```derby 21 | 22 | ``` 23 | ```js 24 | // Equivalent to: 25 | modal.on('close', function() { 26 | self.reset(); 27 | }); 28 | modal.on('fullView', function() { 29 | back.fade(); 30 | }); 31 | ``` 32 | 33 | ## Emitting events 34 | Components can emit custom events to be handled by their parents. 35 | 36 | ```derby 37 | 38 | 39 | ``` 40 | 41 | ```js 42 | //listen 43 | modal.on('fullView', function(foo) { 44 | console.log(foo); 45 | }) 46 | //... 47 | //emit 48 | modal.emit("fullView", foo); 49 | ``` 50 | 51 | 52 | ## Calling peer component methods 53 | 54 | Components and elements can be set as a property on the current controller with the `as=` HTML attribute ([more detail](../views/template-syntax/paths#controller-properties)). This paired with how controller properties are looked up on function calls makes it easy to connect events on components or elements to methods on other components. 55 | 56 | ```derby 57 | 58 | 59 | 60 | ``` 61 | 62 | ```derby 63 | 64 | 65 | ... 66 | 67 | ``` 68 | 69 | ## Component event arguments 70 | 71 | Component events implicitly pass through any emitted arguments. These arguments are added after any explicitly specified arguments in the expression. 72 | 73 | ```derby 74 | 75 | 76 | 77 | 78 | ``` 79 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/components/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/components/lifecycle.md: -------------------------------------------------------------------------------- 1 | # Component lifecycle 2 | 3 | 4 | Components are defined by writing a view template in HTML and a JavaScript controller class. Calling `app.component(MyComponent)` registers the view and controller with a Derby app. After that, an instance of the component class is created whenever its view is rendered. 5 | 6 | 7 | ## Rendering 8 | 9 | Derby components are designed for efficient server-side HTML and client-side DOM rendering with the same code. 10 | 11 | 12 | ### Client-side rendering 13 | 14 | *1. `new MyComponent.DataConstructor()`:* First, Derby instantiates an object that is used for the component's model data. This class should be defined to set default values that can be used in either the view or the controller of the component. Derby will set two properties, `id` and `$controller`, on this object. `id` is a string id unique to the component, and `$controller` is a reference to the component instance. These properties are set in the model data so that they can be used in the view. 15 | 16 | *2. `new MyComponent(context, data)`:* Derby calls the component's constructor with a rendering context object and the model data instance. As is idiomatic in JavaScript, it is recommended that `super(context, data)` is called at the top, and instance properties are set following that. Note that attributes passed in from the view are not yet set on the model. 17 | 18 | *3. `Component(context, data)`:* Derby's component constructor creates the component's `id` and `model`. If the custom constructor does not call super, Derby calls this method immediately after. Effectively, components behave like there was a call to super at the end of their constructor when super isn't called explicitly. 19 | 20 | *4. Attribute values from the view are set on the model:* Attribute values are passed into compoenents via the view. Derby sets and binds these values to the model after invoking the constructor. 21 | 22 | *5. `'init'` event:* Derby emits an `'init'` event on the component instance before calling the init method. This event is rarely used. However, it is provided in case containing components need to obtain a reference to a component instance before it renders. 23 | 24 | *6. `MyComponent.init(model)`:* Init is called once Derby has completed all steps to initialize the component and before rendering. All custom code that initializes data in the model prior to rendering, such as reactive functions, should be placed within the component's `init()` implementation. 25 | 26 | *7. Rendering:* Following `init()`, Derby renders the component's view and inserts it into the DOM. Hooks defined in the view, such as the `as` attribute for assigning elements and components to controller properties or `on-` attributes for adding event listeners happen at render time as well. 27 | 28 | *8. `'create'` event:* On the client only, Derby emits a `'create'` event on the component instance before calling the create method. Similar to the `'init'` event, this method is provided in case containing components need to obtain a reference to a component instance. However, the create event only happens on the client, and it is emitted after the component is rendered and inserted into the DOM. 29 | 30 | *9. `MyComponent.create(model, dom)`:* Create is called once the component is rendered and inserted into the DOM. Custom code that adds model or DOM event listeners should be placed within the component's `create()` implementation. 31 | 32 | 33 | ### Server-side rendering 34 | 35 | Steps 1-7 of the rendering process are the same for server-side rendering. However, instead of using DOM methods to render, Derby returns a string of HTML. The key difference between client-side and server-side rendering is that `create()` is called only on the client, and server-side rendering happens within Node.js instead of the browser. 36 | 37 | There are a number of differences between Node.js and a browser environment that must be taken into account: 38 | 39 | * Server time and client time will differ. Client-time may differ by small or large amounts, and its accuracy cannot be ensured. 40 | 41 | * Servers do not have a session-appropriate timezone or locale, so JavaScript's `Date` and `Intl` features cannot be used without ensuring that the server and client are using matching implementations and configurations. 42 | 43 | * No DOM methods are available on the server. 44 | 45 | * There is no need to create bindings and event listeners on the server, because data will not be dynamically changing. 46 | 47 | * Servers are multi-tenent and long lived, so be careful to avoid global state in components. This is also a best practice in client-only applications, but it is especially important when code is executed on both the server and the client. On the server, shared state could lead to data being leaked from one session to another, and minor memory leaks in long-lived processes can build up and crash a server. 48 | 49 | 50 | ### Server-side rendering + Client-side attachment 51 | 52 | Out of the box, Derby is optimized for server + client-side rendering. This can greatly improve perceived load time, since the browser can display the application before its scripts have loaded or executed on the client. 53 | 54 | In this type of rendering, the server renders HTML, and the browser creates DOM nodes from HTML. Then, Derby does a special kind of rendering called "attachment," where it does all of the client-side rendering steps. However, it uses the existing DOM nodes in the page rather than creating new DOM nodes. 55 | 56 | Therefore, component code must be deterministic. Pure code, where the same inputs return the same results and there are no side effects, is best. Common pitfalls: 57 | 58 | * Components should not rely on external inputs, such as `Date.now()` or the result of `Math.random()` in rendering, because their results will differ on subsequent calls. You can compute the values ahead of time and store them in the model on `_session` or `_page`, so that the values will be the same when rendered on the server and after the initial page load in the client. You may also choose to render certain values in the client only, by setting them in `create()`. 59 | 60 | * Sorting should use a stable comparison. 61 | 62 | * Rendering components should not modify persistent state or have other side effects. 63 | 64 | 65 | In addition, Derby requires that parsing the HTML in templates produces a matching DOM. Common pitfalls: 66 | 67 | * Templates must be valid HTML. For example, `

`, is invalid HTML and will produce a DOM such as `

`. This is because the [`

` element](https://html.spec.whatwg.org/multipage/grouping-content.html#the-p-element) may contain only [phrasing content](https://html.spec.whatwg.org/multipage/dom.html#phrasing-content), and the start of a `

` closes the `

`. 68 | 69 | * Templates must explicitly include all [optional tags](https://html.spec.whatwg.org/multipage/syntax.html#optional-tags). For example, `
` is valid HTML, but it will produce the DOM `
`. For simplicity, Derby does not attempt to implement these rules and requires that optional tags be written out. 70 | 71 | * All non-void elements must be explicitly closed. For example, `

  • One
  • Two
` is valid HTML, because an `
  • ` elements' end tags are implied. Derby requires that this be written out fully as `
    • One
    • Two
    `. ([Void elements](https://html.spec.whatwg.org/multipage/syntax.html#void-elements) like `` only have a start tag and the end tag must not be specified.) 72 | 73 | To test whether an HTML fragment will work in a Derby template, use an [HTML validator](https://validator.nu/) and check that setting then reading it back as `innerHTML` returns the same string. 74 | 75 | ```js 76 | var html = '

    '; 77 | var div = document.createElement('div'); 78 | div.innerHTML = html; 79 | html === div.innerHTML; 80 | ``` 81 | 82 | 83 | ## Cleanup 84 | 85 | When a binding causes a component to be removed from the DOM, Derby internally calls its `destroy()` method. (This method should not be invoked manually.) Destroying a component removes its DOM listeners, destroys its model data and model listeners, removes references created by `as` attributes in views, and removes all of Derby's internal references to the component and bindings within the component. Each of these is important for avoiding memory leaks. 86 | 87 | Using Derby's built-in features to add DOM listeners, model listeners, and bind asynchronous callbacks generally avoids the need to implement custom cleanup code. If custom cleanup code is needed, it can be implemented by listening to the component's `'destroy'` event or checking whether the component's `isDestroyed` property is `true`. 88 | 89 | 90 | ### Singleton (stateless) components 91 | 92 | Creating a model per component, binding component attributes, and cleaning up component models and bindings can add significant overhead. However, Derby's template syntax is very expressive, and many components can be written in a stateless manner with no need for their own model. 93 | 94 | In this case, it is best to declare the component as a "singleton" component. A singleton component is also implemented with a JavaScript class for a controller, but Derby will only instantiate the class once and reuse the same instance of the class each time the component's view is rendered. Derby will not create a model or other properties on the controller, since its instance can be used in multiple places simultaneously. In addition, rendering a singleton component does not invoke `init()`, `create()`, or `destroy()`. 95 | 96 | Since singleton components do not have a model, only attribute paths may be used in views. Singleton controllers should consist of only pure functions. 97 | 98 | When a component is used many times on a page, such as a repeated item in a list or a commonly used UI element, it is best to write it statelessly for better performance. View partials are the most lightweight, singleton components allow use of custom JavaScript, and full components have their own model state. 99 | 100 | ```derby 101 | 102 |
    103 | {{getInitials(@user.fullName)}} 104 |
    105 | ``` 106 | 107 | ```js 108 | app.component('user-icon', class UserIcon { 109 | static singleton = true; 110 | getInitials(fullName) { 111 | return fullName.split(' ').map(name => name[0]).join(''); 112 | } 113 | }); 114 | ``` 115 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/components/overview.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Components are the building blocks of Derby applications. A component is a view associated with a controller class. The [view](views) is implemented as a Derby template and the controller is implemented as a JavaScript [class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) or [constructor function](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object-oriented_JS). Derby creates an instance of the controller class each time it renders the component view. 4 | 5 | 6 | ## Reuse and organization 7 | 8 | Components are reusable UI pieces, similar to custom HTML elements. In addition, they are the recommended way to structure complex applications as modular parts with clear inputs and outputs. Each significant unit of UI functionality should be its own component. 9 | 10 | Components can be rendered on the server and the client, so the same code can produce static HTML, server-rendered dynamic applications, and client-rendered applications. 11 | 12 | 13 | ## Encapsulation 14 | 15 | Each component has a scoped model in its own namespace. Data or references to the component's parent are passed in via view attributes. If you're familiar with it, this structure is similar to the [Model-View-ViewModel (MVVM) pattern](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel)—a component's scoped model is a ViewModel. 16 | 17 | 18 | ## Tabs Example 19 | 20 | ### index.html 21 | ```derby 22 | 23 | 24 | 25 |

    Some stuff here

    26 |
    27 | 28 |

    More stuff

    29 |
    30 |
    31 | ``` 32 | 33 | ### tabs.html 34 | ```derby 35 | 36 |
      37 | {{each @pane as #pane, #i}} 38 | {{with #i === selectedIndex as #isActive}} 39 |
    • 40 | {{if #isActive}} 41 | {{#pane.title}} 42 | {{else}} 43 | {{#pane.title}} 44 | {{/if}} 45 |
    • 46 | {{/with}} 47 | {{/each}} 48 |
    49 | {{each @pane as #pane, #i}} 50 |
    {{#pane.content}}
    51 | {{/each}} 52 | ``` 53 | 54 | ### tabs.js 55 | ```js 56 | module.exports = Tabs; 57 | 58 | function Tabs() {} 59 | Tabs.view = __dirname + '/tabs.html'; 60 | 61 | Tabs.DataConstructor = function() { 62 | this.selectedIndex = 0; 63 | }; 64 | 65 | Tabs.prototype.select = function(index) { 66 | this.model.set('selectedIndex', index); 67 | }; 68 | ``` 69 | 70 | ### tabs.ts 71 | ```ts 72 | const Component = require('derby').Component; 73 | 74 | export = Tabs; 75 | 76 | class TabsData { 77 | selectedIndex: number = 0; 78 | } 79 | class Tabs extends Component { 80 | static view = __dirname + '/tabs.html'; 81 | static DataConstructor = TabsData; 82 | selectedIndex = this.model.at('selectedIndex'); 83 | 84 | select(index: number): void { 85 | this.selectedIndex.set(index); 86 | } 87 | } 88 | ``` 89 | 90 | ### tabs.coffee 91 | ```coffee 92 | module.exports = class Tabs 93 | @view: __dirname + '/tabs.html' 94 | 95 | @DataConstructor: -> 96 | @selectedIndex = 0 97 | 98 | select: (index) -> 99 | @model.set 'selectedIndex', index 100 | ``` 101 | 102 |

    103 | 104 | (The above example uses [derby-standalone](https://github.com/derbyjs/derby-standalone), a client-side only build of Derby.) 105 | 106 | 107 | ## Todos example 108 | 109 | ```derby 110 | 111 | 114 | 115 | 119 | 120 | {{if _page.items.length}} 121 | 124 | 125 | {{/if}} 126 | 127 | 128 |
    129 | 130 | 131 |
    132 | 133 | 134 |
      135 | {{each items as #item, #i}} 136 |
    • 137 | 141 | 142 |
    • 143 | {{/each}} 144 |
    145 | 146 | 147 | 150 | ``` 151 | 152 | ```js 153 | app.component('todos-new', class TodosNew { 154 | submit() { 155 | const value = this.model.del('value'); 156 | this.emit('submit', value); 157 | } 158 | }); 159 | 160 | app.component('todos-list', class TodosList { 161 | add(text) { 162 | if (!text) return; 163 | this.model.push('items', {text}); 164 | } 165 | remove(index) { 166 | this.model.remove('items', index); 167 | } 168 | }); 169 | 170 | app.component('todos-footer', class TodosFooter { 171 | static singleton = true; 172 | remaining(items) { 173 | if (!items) return 0; 174 | return items.filter(item => !item.done).length; 175 | } 176 | }); 177 | ``` 178 | 179 |

    180 | 181 | 182 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/components/scope.md: -------------------------------------------------------------------------------- 1 | # Scope 2 | 3 | Each component instance has its own scoped model, providing it isolation from model data for other components and remote collection data. 4 | 5 | ## Attributes and data bindings 6 | 7 | The most direct way to get data into a component is to pass in a reference or a literal as a view attribute. 8 | 9 | ```derby 10 | 11 | 12 | 13 | 14 |
      15 | {{each data as #user}} 16 |
    • {{#user.name}}
    • 17 | {{/each}} 18 |
    19 | {{num + 10}} 20 | ``` 21 | 22 | See [view attributes](../views/template-syntax/view-attributes) for more information. 23 | 24 | 25 | ## Root model 26 | 27 | There are times when accessing data in the root model is desirable from within the component. This can be achieved both in the template and in the controller. 28 | 29 | ```derby 30 | 31 | 32 | {{#root.users[userId]}} 33 | ``` 34 | 35 | ```js 36 | var users = this.model.root.get("users"); 37 | var user = users[userId]; 38 | // or 39 | var $users = this.model.scope("users"); 40 | var user = $users.get(userId); 41 | ``` 42 | 43 | 44 | ### With block 45 | See the documentation for [with blocks](../views/template-syntax/blocks#with) to pass in data with an alias. 46 | 47 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/components/view-partials.md: -------------------------------------------------------------------------------- 1 | # View partials 2 | 3 | This page goes into more detail about how view partials relate to components. For more general concepts, see the [template syntax](../views/template-syntax) documentation. 4 | 5 | While a component's controller is associated with a single view, it can contain sub-views defined as view partials. Components can also accept other views passed in as attributes. 6 | 7 | ## Scope 8 | By default a view partial inherits the scope where it is instantiated. 9 | 10 | ```derby 11 | 12 | {{foo}} 13 | {{with #root.bar as #bar}} 14 | 15 | {{/with}} 16 | 17 | 18 | i can render {{foo}} and {{#bar}} 19 | ``` 20 | A view partial associated with a component follows the [component scope](scope) rules. A view partial used inside a component will inherit the scope of the component. 21 | 22 | ### extend 23 | It is possible to override another component's functionality while preserving its view. You can do this with the `extend` keyword. 24 | 25 |

    26 | 27 | 28 | 29 | 30 | ### import 31 | If you just want to reuse a view partial the `import` keyword is probably more appropriate. See the [namespaces and files](../views/namespaces-and-files#structuring-views-in-multiple-files) documentation for more details. 32 | 33 | 34 | ## Programmatic view management 35 | 36 | > `view = this.getView(name)` 37 | > * `name` the name of the view 38 | > * `view` a template object representing the view 39 | 40 | It is possible to access the views in a component's namespace from the controller. This may be used in conjunction with `setAttribute` to override a component's default rendering. 41 | An example use case would be to set a default template and then allow the user of the component to pass in a template to override the default. 42 | 43 | See the [attributes](scope#attributes-vs-model-data) documentation for more information on using `setAttribute`. 44 | 45 | 46 | ## Component tracking 47 | Derby components are tracked in the DOM with an HTML comment tag. This allows components to be responsible for arbitrary DOM content, for example two table rows that otherwise cannot be wrapped by any other DOM elements. 48 | 49 | ```derby 50 | 51 | ``` 52 | 53 | ## Debugging 54 | 55 | A relatively quick way to inspect a component for debugging is to find its comment in the browser's DOM inspector. 56 | In modern browsers, clicking on the comment allows you to reference it in the console with `$0`. 57 | Once you have a reference to the comment tag, you can access its controller with `$0.$component` and its model data with `$0.$component.model.get()` 58 | 59 | 60 | 61 | ### derby-debug 62 | There is a plugin which makes accessing your components from the console even more accessible that is recommended for development. 63 | Read more about [derby-debug](https://github.com/derbyjs/derby-debug). 64 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/models/backends.md: -------------------------------------------------------------------------------- 1 | # Backends 2 | 3 | Racer stores are backed by ShareDB, which is used to persist data, perform queries, keep a journal of all operations, and pub/sub operations and changes to queries. Currently, ShareDB has [two pub/sub adapters](https://github.com/share/sharedb#database-adapters): one for in memory and one for Redis based pub/sub. ShareDB supports in memory or MongoDB storage. The database adapter [ShareDBMongo](https://github.com/share/sharedb-mongo) 4 | is backed by a real Mongo database and full query support. ShareDB is written with support for additional database adapters in mind. 5 | 6 | Getting started with a single-process server and MongoDB: 7 | 8 | ```js 9 | var derby = require('derby'); 10 | var ShareDbMongo = require('sharedb-mongo'); 11 | 12 | var db = new ShareDbMongo('mongodb://localhost:27017/test'); 13 | var backend = derby.createBackend({db: db}); 14 | var model = backend.createModel(); 15 | ``` 16 | 17 | The above examples use the in-process driver by default. In a production environment, you'll want to scale across multiple frontend servers and support updating data in other processes, such as migration scripts and additional services. For this, you should use the [ShareDB Redis pub/sub adapter](https://github.com/share/sharedb-redis-pubsub). ShareDB requires Redis 2.6 or newer, since it uses Lua scripting commands. 18 | 19 | ```js 20 | var derby = require('derby'); 21 | var ShareDbMongo = require('sharedb-mongo'); 22 | var RedisPubSub = require('sharedb-redis-pubsub'); 23 | 24 | var db = new ShareDbMongo('mongodb://localhost:27017/test'); 25 | var backend = derby.createBackend({ 26 | db: db, 27 | pubsub: new RedisPubSub() 28 | }); 29 | var model = backend.createModel(); 30 | ``` 31 | 32 | See [ShareDBMongo](https://github.com/share/sharedb-mongo) and [ShareDB Redis](https://github.com/share/sharedb-redis-pubsub) documentation for more information on configuration options. 33 | 34 | The Redis driver supports flushing all data from Redis or starting with an empty Redis database with journal and snapshot data in MongoDB. Thus, it is OK to start with a basic deployment using only a single process and add Redis later or to flush the Redis database if it becomes corrupt. 35 | 36 | ## Mapping between database and model 37 | 38 | Racer paths are translated into database collections and documents using a natural mapping: 39 | 40 | ```bash 41 | collection.documentId.documentProperty 42 | ``` 43 | 44 | ShareDB Mongo will add the following properties to Mongo documents for internal use: 45 | * `_m.ctime` - Timestamp when the ShareDB document was created 46 | * `_m.mtime` - Timestamp when the ShareDB document was last modified 47 | * `_type` - [OT type](https://github.com/share/sharedb#data-model) 48 | * `_v` - [Snapshot version](https://github.com/share/sharedb#data-model) 49 | 50 | In addition to `ctime` and `mtime`, custom metadata properties can be added to `_m` with middleware that modifies `snapshot.m` in apply or commit. 51 | 52 | Since these underscore-prefixed properties are for ShareDB's internal use, ShareDB Mongo will strip out these properties (`_m`, `_type`, and `_v`) as well as `_id` when it returns the document from Mongo. The `_id` is removed because Racer adds an `id` alias to all local documents. This alias references the `_id` property of the original Mongo document. 53 | 54 | If a document is an object, it will be stored as the Mongo document directly. For example, 55 | 56 | ```js 57 | { 58 | make: "Ford", 59 | model: "Mustang", 60 | year: 1969, 61 | _m: { 62 | ctime: 1494381632731, 63 | mtime: 1494381635994 64 | }, 65 | _type: "http://sharejs.org/types/JSONv0", 66 | _v: 12 67 | } 68 | ``` 69 | 70 | If it is another type (e.g. [Plaintext OT Type](https://github.com/ottypes/text)), the value will be nested under a property on the Mongo document called `_data`. 71 | 72 | ```js 73 | { 74 | _data: "This is a text message.", 75 | _m: { 76 | ctime: 1494381632731, 77 | mtime: 1494381635994 78 | }, 79 | _type: "http://sharejs.org/types/text", 80 | _v: 12 81 | } 82 | ``` 83 | 84 | It is not possible to set or delete an entire collection, or get the list of collections via the Racer API. 85 | 86 | ## Loading data into a model 87 | 88 | The `subscribe`, `fetch`, `unsubscribe`, and `unfetch` methods are used to load and unload data from ShareJS. These methods don't return data directly. Rather, they load the data into a model. Once loaded, the data are then accessed via model getter methods. 89 | 90 | `subscribe` and `fetch` both return data initially, but subscribe also registers with pub/sub on the server to receive ongoing updates as the data change. 91 | 92 | > `model.subscribe(items..., callback(err))` 93 | > `model.fetch(items..., callback(err))` 94 | > `model.unsubscribe(items..., callback(err))` 95 | > `model.unfetch(items..., callback(err))` 96 | > * `items` Accepts one or more subscribe-able items, including a document path, scoped model, or query 97 | > * `callback` Calls back once all of the data for each query and document has been loaded or when an error is encountered 98 | 99 | Avoid subscribing or fetching queries by document id like `model.query('users', {_id: xxx})`. You can achieve the same result passing `'users.xxx'` or `model.at('users.xxx')` to subscribe or fetch, and it is much more efficient. 100 | 101 | If you only have one argument in your call to subscribe or fetch, you can also call `subscribe`, `fetch`, `unsubscribe`, and `unfetch` on the query or scoped model directly. 102 | 103 | ```js 104 | var user = model.at('users.' + userId); 105 | var todosQuery = model.query('todos', {creatorId: userId}); 106 | model.subscribe(user, todosQuery, function(err) { 107 | if (err) return next(err); 108 | console.log(user.get(), todosQuery.get()); 109 | page.render(); 110 | }); 111 | ``` 112 | 113 | Racer internally keeps track of the context in which you call subscribe or fetch, and it counts the number of times that each item is subscribed or fetched. To actually unload a document from the model, you must call the unsubscribe method the same number of times that subscribe is called and the unfetch method the same number of times that fetch is called. However, you generally don't need to worry about calling unsubscribe and unfetch manually. 114 | 115 | Instead, the `model.unload()` method can be called to unsubscribe and unfetch from all of the subscribes and fetches performed since the last call to unload. Derby calls this method on every full page render right before entering a route. By default, the actual unsubscribe and unfetch happens after a short delay, so if something gets resubscribed during routing, the item will never end up getting unsubscribed and it will callback immediately. 116 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/models/data-loading-contexts.md: -------------------------------------------------------------------------------- 1 | # Data loading contexts 2 | 3 | As data is loaded into a model with calls to fetch and subscribe, Racer tracks the number of fetches and subscribes per document path and query. Data is not removed from a model until it is released by calling unfetch and unsubscribe the matching number of times for each document or query. For example, after calling `subscribe()` on a query twice, then `unsubscribe()` once, the query would remain subscribed. It would be unsubscribed and its data would be removed from the model only after `unsubscribe()` was called once more. 4 | 5 | This behavior is helpful, since multiple parts of an application may need the same resource, but they may want perform data loading and unloading independently. For example, an edit dialog may be opened and closed while some of the same data may be displayed in a list; or a migration script may fetch data in batches in order to process a large amount of data without loading all of it into memory simultaneously. 6 | 7 | Contexts provide a way to track a group of related fetches and subscribes. In addition, they provide an `unload()` method that unfetches and unsubscribes the corresponding number of times. By default, all fetches and subscribes happen within the `'root'` context. Additional context names may be used to isolate the loading and unloading of data within the same model for independent purposes. 8 | 9 | > `childModel = model.context(name)` 10 | > * `name` A string uniquely identifying a context. Calling `model.context()` again with the same string will refer to the same context. By default, models have the context name `'root'` 11 | > * `childModel` Returns a model with a context of `name`, overriding the parent model's context name. All fetch, subscribe, and unload actions performed on this childModel will have this context 12 | 13 | > `model.unload([name])` 14 | > * `name` *(optional)* Unfetch and unsubscribe from all documents and queries for the corresponding number of times they were fetched and subscribed. This will end subscriptions and remove the data from the model if no remaining fetches or subscribes hold the data in the model under a different context. Defaults to the current model context name. Specifying a `name` argument overrides the default 15 | 16 | > `model.unloadAll()` 17 | > * Unload each context within a model. Results in all remotely loaded data being removed from a model. (Data within [local collections](paths#local-and-remote-collections) will remain.) 18 | 19 | ## Usage example 20 | 21 | ```js 22 | function openTodos(model) { 23 | // Create a model with a load context inheriting from the current model 24 | var dialogModel = model.context('todosDialog'); 25 | // Load data 26 | var userId = dialogModel.scope('_session.userId').get(); 27 | var user = dialogModel.scope('users.' + userId); 28 | var todosQuery = dialogModel.query('todos', {creatorId: userId}); 29 | dialogModel.subscribe(user, todosQuery, function(err) { 30 | if (err) throw err; 31 | // Delay display until required data is loaded 32 | dialogModel.set('showTodos', true); 33 | }); 34 | } 35 | 36 | function closeTodos(model) { 37 | model.set('showTodos', false); 38 | // Use the same context name to unsubscribe 39 | model.unload('todosDialog'); 40 | } 41 | ``` 42 | 43 | ## Automatic unloading on page navigation 44 | 45 | Derby uses Racer model contexts to unload the data for the previous page render when it performs client-side routing and a full-page render. When moving away from a page and before calling into the route for the new page, Derby calls `unloadAll()`, removing the data from all subscribes and fetches performed on the prior page. 46 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/models/events.md: -------------------------------------------------------------------------------- 1 | # Events 2 | 3 | Model events are based on the standard [Node.js EventEmitter](https://nodejs.org/docs/latest/api/events.html) methods, and they support the same methods: `on`, `once`, `removeListener`, `emit`, etc. 4 | 5 | ## Mutation events 6 | 7 | Racer emits events whenever it mutates data via `model.set()`, `model.push()`, etc. It also emits events when data is remotely updated via a subscription. These events provide an entry point for an app to react to a specific data mutation or pattern of data mutations. The events might not be exactly the same as the methods that created them, since they can be transformed via OT. 8 | 9 | `model.on()` and `model.once()` accept a second argument for these mutation events. The second argument is a path pattern that will filter emitted events, calling the handler function only when a mutator matches the pattern. Path patterns support a single segment wildcard (`*`) anywhere in a path, and a multi-segment wildcard (`**`) at the end of the path. The multi-segment wildcard alone (`'**'`) matches all paths. 10 | 11 | > `listener = model.on(method, path, [options], eventCallback)` 12 | > * `method` Name of the mutator method: `'change'`, `'insert'`, `'remove'`, `'move'`, `'load'`, `'unload'`, or `'all'` 13 | > * `path` Pattern matching the path being mutated. For example: `'_page.user'`, `'users.*.name'`, `'users.*'`, `'users.**'` / `'users**'`, or `'**'`. `**` is valid only by itself or at the end of the path. 14 | > * `options` (optional) 15 | > * `useEventObjects` - If true, the callback is called with a structured event object instead of with a variable number of arguments. _Introduced in [racer@0.9.6](https://github.com/derbyjs/racer/releases/tag/v0.9.6)._ 16 | > * `eventCallback` Function to call when a matching method and path are mutated 17 | > * Returns `listener` - the listener function subscribed to the event emitter. This is the function that should be passed to `model.removeListener` 18 | 19 | ### `eventCallback` with `{useEventObjects: true}` 20 | 21 | _Introduced in [racer@0.9.6](https://github.com/derbyjs/racer/releases/tag/v0.9.6)._ 22 | 23 | > `eventCallback(event, captures)` 24 | > * `event` - _Object_ - An instance of an Event object (see below) 25 | > * `captures` - _Array_ - The captured path segments, one item per wildcard in the pattern. Each `'*'` results in a string, and a `'**'` results in a sub-array of strings. 26 | 27 | Event objects: 28 | 29 | > `ChangeEvent { value, previous, passed }` 30 | > * `type: 'change'` 31 | > * `value` The current value at the path that was changed. Will be `undefined` for a deletion. 32 | > * `previous` The previous value at the path. Will be `undefined` if the path was previously unset. 33 | 34 | > `InsertEvent { index, values, passed }` 35 | > * `type: 'insert'` 36 | > * `index` The index at which items were inserted 37 | > * `values` An array of values that were inserted. Always an array, even if only one item was pushed, unshifted, or inserted. 38 | 39 | > `RemoveEvent { index, removed, passed }` 40 | > * `type: 'remove'` 41 | > * `index` The index at which items were removed 42 | > * `removed` An array of values that were removed. Always an array, even if only one item was popped, shifted, or removed 43 | 44 | > `MoveEvent { from, to, howMany, passed }` 45 | > * `type: 'move'` 46 | > * `from` The index from which items were moved 47 | > * `to` The index to which items were moved 48 | > * `howMany` How many items were moved 49 | 50 | > `LoadEvent { document, passed }` 51 | > * `type: 'load'` 52 | > * `document` This event fires when a document is loaded via a subscription or fetch. This the value of the newly loaded document object. 53 | 54 | > `UnloadEvent { previousDocument, passed }` 55 | > * `type: 'unload'` 56 | > * `previousDocument` This event fires when a document is removed from the model via unsubscribe or unfetch. This is the value of the document object that was unloaded. 57 | 58 | The `event.type` is useful for distinguising the actual event type when listening to `'all'`. 59 | 60 | ```js 61 | // Matches model.push('messages', message) 62 | model.on('insert', 'messages', {useEventObjects: true}, function(insertEvent) { 63 | console.log(insertEvent.values, 'inserted at index', insertEvent.index); 64 | }); 65 | 66 | // Matches model.set('todos.4.completed', true), etc. 67 | model.on('change', 'todos.*.completed', {useEventObjects: true}, function(changeEvent, captures) { 68 | console.log('todos.' + captures[0] + ' set to ' + changeEvent.value); 69 | }); 70 | 71 | // Matches all events 72 | model.on('all', '**', {useEventObjects: true}, function(event, captures) { 73 | var starStarSegments = captures[0]; 74 | console.log(event.type + ' at ' + starStarSegments.join('.') + ':', event); 75 | }); 76 | ``` 77 | 78 | ### `eventCallback` when `useEventObjects` is false or undefined 79 | 80 | The event callback receives a number of arguments based on the path pattern and method. The arguments are: 81 | 82 | > `eventCallback([captures...], [eventType], args..., passed)` 83 | > * `captures` The path segment or segments that is passed in only when matching wildcards in the path pattern 84 | > * `eventType` Only the `'all'` event adds the emitted event name after the captures and before the args 85 | > * `args` Event specific arguments. See below 86 | > * `passed` An object with properties provided via `model.pass()`. See description below 87 | 88 | Callbacks for each event type: 89 | 90 | > `changeCallback([captures...], value, previous, passed)` 91 | > * `value` The current value at the path that was changed. Will be `undefined` for objects that were deleted 92 | > * `previous` The previous value at the path. Will be `undefined` for paths set for the first time 93 | 94 | > `insertCallback([captures...], index, values, passed)` 95 | > * `index` The index at which items were inserted 96 | > * `values` An array of values that were inserted. Always an array, even if only one item was pushed, unshifted, or inserted 97 | 98 | > `removeCallback([captures...], index, removed, passed)` 99 | > * `index` The index at which items were removed 100 | > * `removed` An array of values that were removed. Always an array, even if only one item was popped, shifted, or removed 101 | 102 | > `moveCallback([captures...], from, to, howMany, passed)` 103 | > * `from` The index from which items were moved 104 | > * `to` The index to which items were moved 105 | > * `howMany` How many items were moved 106 | 107 | > `loadCallback([captures...], document, passed)` 108 | > * `document` This event fires when a document is loaded via a subscription or fetch. It emits the value of the newly loaded document object 109 | 110 | > `unloadCallback([captures...], previousDocument, passed)` 111 | > * `previousDocument` This event fires when a document is removed from the model via unsubscribe or unfetch. It emits the value of the document object that was unloaded 112 | 113 | ```js 114 | // Matches model.push('messages', message) 115 | model.on('insert', 'messages', function(index, [message]) { 116 | ... 117 | }); 118 | 119 | // Matches model.set('todos.4.completed', true), etc. 120 | model.on('change', 'todos.*.completed', function(todoId, isComplete) { 121 | ... 122 | }); 123 | 124 | // Matches all events - `path` and `event` are passed in to the event callback 125 | model.on('all', '**', function(path, event, args...) { 126 | ... 127 | }); 128 | ``` 129 | 130 | ### Passing data to event listeners 131 | 132 | > `model.pass(object)` 133 | > * `object` An object whose properties will each be set on the `passed` argument 134 | 135 | `model.pass()` can be chained before calling a mutator method to pass an argument to model event listeners. You must pass it an object with a property that identifies the name of the parameter. 136 | 137 | This value is only passed to local listeners, and it is not sent to the server or other clients. It is typically used to identify the originator of a particular mutation so that multiple responses to the same change and infinite loops may be avoided. Such loops could occur for listeners that respond to paths that they may modify. 138 | 139 | On a string insert or string remove mutation, a `'change`' event is emitted, since strings are immutable values, and inserting or removing from a string requires changing its entire value. However, detail on what specifically was inserted or removed is neccessary to implement view bindings properly for realtime collaborative text editing. This additional information is added to the `passed` object. On a string insert, the passed object has an additional property of `$stringInsert: {index: Number, text: String}`. On a string remove, the passed object has an additional property of `$stringRemove: {index: Number, howMany: Number}`. 140 | 141 | ```js 142 | // Logs: 143 | // 'red', {} 144 | // 'green', {message: 'hi'} 145 | 146 | model.on('change', 'color', function(value, previous, passed) { 147 | console.log(value, passed); 148 | }); 149 | model.set('color', 'red'); 150 | model.pass({message: 'hi'}).set('color', 'green'); 151 | ``` 152 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/models/filters-and-sorts.md: -------------------------------------------------------------------------------- 1 | # Filters and sorts 2 | 3 | Filters create a live-updating list from items in an object. The results automatically update as the input items change. 4 | 5 | > `filter = model.filter(inputPath, [additionalInputPaths...], [options], fn)` 6 | > * `inputPath` A path pointing to an object or array. The path's values will be retrieved from the model via `model.get()`, and then each item will be checked against the filter function 7 | > * `additionalInputPaths` *(optional)* Other parameters can be set in the model, and the filter function will be re-evaluated when these parameters change as well 8 | > * `options:` 9 | > * `skip` The number of first results to skip 10 | > * `limit` The maximum number of results. A limit of zero is equivalent to no limit 11 | > * `fn` A function or the name of a function defined via `model.fn()`. The function should have the arguments `function(item, key, object, additionalInputs...)`. Like functions for [`array.filter()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter), the function should return true for values that match the filter 12 | 13 | ```js 14 | app.get('/search-pants', function(page, model, params, next) { 15 | model.subscribe('pants', function(err) { 16 | if (err) return next(err); 17 | model.filter('pants', 'pricing', 'color', 18 | // evaluate whether a pants item matches the search options 19 | function(item, pantsId, pants, pricing, color) { 20 | return item.price >= pricing.minimum 21 | && item.price <= pricing.maximum 22 | && item.color == color; 23 | } 24 | ).ref('_page.pantsList'); // bind the output of the filter 25 | page.render('pants'); 26 | }); 27 | }); 28 | ``` 29 | 30 | If `model.filter()` is called with `null` for the function, it will create a list out of all items in the input object. This can be handy as a way to render all subscribed items in a collection, since only arrays can be used as an input to `{{each}}` template tags. 31 | 32 | > `filter = model.sort(inputPath, [options], fn)` 33 | > * `inputPath` A path pointing to an object or array. The path's values will be retrieved from the model via `model.get()`, and then each item will be checked against the filter function 34 | > * `options:` 35 | > * `skip` The number of first results to skip 36 | > * `limit` The maximum number of results. A limit of zero is equivalent to no limit 37 | > * `fn` A function or the name of a function defined via `model.fn()`. The function should should be a compare function with the arguments `function(a, b)`. It should return the same values as compare functions for [`array.sort()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 38 | 39 | There are two default named functions defined for sorting, `'asc'` and `'desc'`. These functions compare each item with Javascript's less than and greater than operators. See MDN for more info on [sorting non-ASCII characters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Sorting_non-ASCII_characters). 40 | 41 | You may define functions to be used in `model.filter()` and `model.sort()` via [`model.fn()`](functions#named-functions). 42 | 43 | A filter may have both a filter function and a sort function by chaining the two calls: 44 | 45 | ```js 46 | app.on('model', function(model) { 47 | model.fn('expensiveItem', function(item) { 48 | return item.price > 100; 49 | }); 50 | model.fn('priceSort', function(a, b) { 51 | return b.price - a.price; 52 | }); 53 | }); 54 | 55 | app.get('/expensive-pants', function(page, model, params, next) { 56 | model.subscribe('pants', function(err) { 57 | if (err) return next(err); 58 | var filter = model.filter('pants', 'expensiveItem') 59 | .sort('priceSort'); 60 | filter.ref('_page.expensivePants'); 61 | page.render('pants'); 62 | }); 63 | }); 64 | ``` 65 | 66 | ## Methods 67 | 68 | The output of a filter is typically used by creating a reference from it. This sets the data in the model and keeps it updated. 69 | 70 | > `scoped = filter.ref(path)` 71 | > * `path` The path at which to create a refList of the filter's output 72 | > * `scoped` Returns a model scoped to the output path of the ref 73 | 74 | The filter's current value can also be retrieved directly via `filter.get()`. 75 | 76 | > `results = filter.get()` 77 | > * `results` Returns an array of results matching the filter 78 | 79 | As well as by updating its input paths, a filter can be recomputed manually by calling its `filter.update()` method. This can also be used to perform pagination, since the the `filter.skip` and `filter.limit` properties can be modified followed by calling `filter.update()`. 80 | 81 | > `filter.update()` 82 | 83 | ```js 84 | var filter = model.sort('items', {skip: 0, limit: 10}, function(a, b) { 85 | if (a && b) return a.score - b.score; 86 | }); 87 | // Logs first 10 items 88 | console.log(filter.get()); 89 | 90 | filter.skip += filter.limit; 91 | filter.update(); 92 | // Logs next 10 items 93 | console.log(filter.get()); 94 | ``` 95 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/models/getters.md: -------------------------------------------------------------------------------- 1 | # Getters 2 | 3 | Data in the model is accessed with `model.get()`. This method returns values by reference, based on the model's scope and/or the path passed to the get method. Models allow getting undefined paths. The get method will return `undefined` and will not throw when looking up a property below another property that is undefined. 4 | 5 | Internally, model data is represented as collections of documents. Collections must be objects, and documents may be of any type, but are also typically objects. A document's data at a point in time is referred to as its snapshot. This structure allows for some documents to be remote documents synced with the server and some to be local documents only in the client. It also means that models are broken into a similar structure as database collections or tables. 6 | 7 | As model document snapshots change from local or remote mutations, the `model.root.data` object is updated. `model.get()` traverses through the properties of the model's data to lookup and return the appropriate value. 8 | 9 | ```js 10 | model.get('_session.account') === model.root.data._session.account; 11 | ``` 12 | 13 | > `value = model.get([path])` 14 | > * `path` *(optional)* Path of object to get. Not supplying a path will return all data in the model starting from the current scope 15 | > * `value` Current value of the object at the given path. Note that objects are returned by reference and should not be modified directly 16 | 17 | > `shallowCopy = model.getCopy([path])` 18 | > * `path` *(optional)* Path of object to get 19 | > * `shallowCopy` Shallow copy of current value, going only one level deep when returning an object or array 20 | 21 | > `deepCopy = model.getDeepCopy([path])` 22 | > * `path` *(optional)* Path of object to get 23 | > * `deepCopy` Deep copy of current value 24 | 25 | ## Values returned by reference 26 | 27 | `model.get()` returns values by reference. Racer will fail to function correctly if data in the model is manipulated directly instead of via its mutator methods, such as `model.set()`. You should *never* mutate objects returned from `model.get()` directly. 28 | 29 | As a convenience, Racer also provides a `model.getCopy()` method that returns a shallow copy and `model.getDeepCopy()` method that returns a deep copy. It is safe to mutate copied objects. Changes in these objects can then be updated back into the model using `model.setDiffDeep()`. 30 | 31 | ```js 32 | // WARNING: Do NOT directly manipulate objects in the model 33 | var user = model.get('users.' + userId); 34 | user.name = 'John'; 35 | 36 | // Instead, use the model setter methods 37 | var user = model.get('users.' + userId); 38 | model.set('users.' + userId + '.name', 'John'); 39 | 40 | // Or, get a copy and set the difference 41 | var user = model.getDeepCopy('users.' + userId); 42 | user.name = 'John'; 43 | model.setDiffDeep('users.' + userId, user); 44 | ``` 45 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/models/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/models/overview.md: -------------------------------------------------------------------------------- 1 | # Models 2 | 3 | DerbyJS models are provided by [Racer](https://github.com/derbyjs/racer), a realtime model synchronization engine. By building on ShareDB, Racer enables multiple users and services to interact with the same data objects with realtime conflict resolution. Racer models have a simple getter/setter and event interface for writing application logic. 4 | 5 | ## Racer and ShareDB 6 | 7 | Racer provides a single interface for working with local data stored in memory and remote data synced via ShareDB. It works equally well on the server and the browser, and it is designed to be used independently from DerbyJS. This is useful when writing migrations, data-only services, or integrating with different view libraries. 8 | 9 | Remotely synced data is stored via [ShareDB](https://github.com/share/sharedb), which means that different clients can modify the same data at the same time. ShareDB uses [Operational Transformation (OT)](https://en.wikipedia.org/wiki/Operational_transformation) to automatically resolve conflicts in realtime or offline. 10 | 11 | On the server, Racer provides a `store`, which configures a connection to a database and pub/sub adapter. Every store connected to the same database and pub/sub system is synchronized in realtime. 12 | 13 | Stores create `model` objects. Models have a synchronous interface similar to interacting directly with objects. They maintain their own copy of a subset of the global state. This subset is defined via [subscriptions](models/backends#loading-data-into-a-model) to certain queries or documents. Models perform operations independently, and they automatically synchronize their state. 14 | 15 | Models emit events when their contents are updated, which DerbyJS uses to update the view in realtime. 16 | 17 | ## Creating models 18 | 19 | Derby provides a model when calling application routes. On the server, it creates an empty model from the `store` associated with an app. When the server renders the page, the model is serialized. It is then reinitialized into the same state on the client. This model object is passed to app routes rendered on the client. 20 | 21 | Derby uses the model supplied by the store.modelMiddleware by calling `req.getModel()`. To pass data from server-side express middleware or routes, the model can be retrieved via this same method and data can be set on it before passing control to the app router. 22 | 23 | If you would like to get or set data outside of the app on the server, you can create models directly via `store.createModel()`. 24 | 25 | > `model = store.createModel(options)` 26 | > * `options:` 27 | > * `fetchOnly` Set to true to make model.subscribe calls perform a fetch instead 28 | > * `model` Returns a model instance associated with the given store 29 | 30 | ## Store 31 | 32 | Typically, a project will have only one store, even if it has multiple apps. It is possible to have multiple stores, but a model can be associated with only a single store, and a page can have only a single model. 33 | 34 | > `store = derby.createStore(options)` 35 | > * `options` See the [Backends](backends) section for information on configuration 36 | > * `store` Returns a Racer store instance 37 | 38 | ### Methods 39 | 40 | > `middleware = store.modelMiddleware()` 41 | > * `middleware` Returns a connect middleware function 42 | 43 | The model middleware adds a `req.getModel()` function which can be called to create or get a model (if one was already created) for a given request. It also closes this model automatically at the end of the request. 44 | 45 | Model's created from `req.getModel()` specify the option `{fetchOnly: true}`. This means that calls to `model.subscribe()` actually only fetch data and don't subscribe. This is more efficient during server-side rendering, since the model is only created for long enough to handle the route and render the page. The model then gets subscribed when it initializes in the browser. 46 | 47 | ```js 48 | var expressApp = express(); 49 | expressApp.use(store.modelMiddleware()); 50 | 51 | expressApp.get('/', function(req, res, next) { 52 | var model = req.getModel(); 53 | }); 54 | ``` 55 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/models/paths.md: -------------------------------------------------------------------------------- 1 | # Paths 2 | 3 | All model operations happen on paths which represent nested JSON objects. These paths must be globally unique within a particular database and Redis journal. 4 | 5 | For example, the model data: 6 | 7 | ```js 8 | { 9 | title: 'Fruit store', 10 | fruits: [ 11 | { name: 'banana', color: 'yellow' }, 12 | { name: 'apple', color: 'red' }, 13 | { name: 'lime', color: 'green' } 14 | ] 15 | } 16 | ``` 17 | 18 | Would have paths like `title`, `fruits.1`, and `fruits.0.color`. Any path segment that is a number must be an index of an array. 19 | 20 | > **WARNING** If you want to use an id value that is a number as a path segment, be careful to prefix this with another character, such as `_` before setting it. Otherwise, you will accidentally create a gigantic array and probably run out of memory. For example, use a path like: `items._1239182389123.name` and never `items.1239182389123.name`. 21 | 22 | ## Local and remote collections 23 | 24 | Collection names (i.e. the first path segment) that start with an underscore (`_`) or dollar sign (`$`) are local to a given model and are not synced. All paths that start with another character are remote, and will be synced to servers and other clients via ShareJS. Collections that begin with dollar signs are reserved for use by Racer, Derby, or extensions, and should not be used for application data. 25 | 26 | Almost all non-synced data within an application should be stored underneath the `_page` local collection. This enables Derby to automatically cleanup as the user navigates between pages. Right before rendering a new page, Derby calls `model.destroy('_page')`, which removes all data, references, event listeners, and reactive functions underneath the `_page` collection. If you have some data that you would like to be maintained between page renders, it can be stored underneath a different local collection. This is useful for setting data on the server, such as setting `_session.userId` in authentication code. However, be very careful when storing local data outside of `_page`, since bleeding state between pages is likely to be a source of unexpected bugs. 27 | 28 | ## Scoped models 29 | 30 | Scoped models provide a more convenient way to interact with commonly used paths. They support the same methods, and they provide the path argument to accessors, mutators, event methods, and subscription methods. Also, wherever a path is accepted in a racer method, a scoped model can typically be used as well. 31 | 32 | > `scoped = model.at(subpath)` 33 | > * `subpath` The relative reference path to set. The path is appended if called on a scoped model 34 | > * `scoped` Returns a scoped model 35 | 36 | > `scoped = model.scope([path])` 37 | > * `path` *(optional)* The absolute reference path to set, or the root path by default. This will become the scope even if called on a scoped model. May be called without a path to get a model scoped to the root 38 | > * `scoped` Returns a scoped model 39 | 40 | > `scoped = model.parent([levels])` 41 | > * `levels` *(optional)* Defaults to 1. The number of path segments to remove from the end of the reference path 42 | > * `scoped` Returns a scoped model 43 | 44 | > `path = model.path([subpath])` 45 | > * `subpath` *(optional)* A relative reference path to append. Defaults to the current path 46 | > * `path` Returns the reference path if applicable 47 | 48 | > `isPath = model.isPath(subpath)` 49 | > * `subpath` A relative reference path or scoped model 50 | > * `isPath` Returns true if the argument can be interpreted as a path, false otherwise 51 | 52 | > `segment = model.leaf()` 53 | > * `segment` Returns the last segment for the reference path. Useful for getting indices, ids, or other properties set at the end of a path 54 | 55 | ```js 56 | room = model.at('_page.room'); 57 | 58 | // These are equivalent: 59 | room.at('name').set('Fun room'); 60 | room.set('name', 'Fun room'); 61 | 62 | // Logs: {name: 'Fun room'} 63 | console.log(room.get()); 64 | // Logs: 'Fun room' 65 | console.log(room.get('name')); 66 | ``` 67 | 68 | ## GUIDs 69 | 70 | Models provide a method to create globally unique ids. These can be used as part of a path or within mutator methods. 71 | 72 | > `guid = model.id()` 73 | > * `guid` Returns a globally unique identifier that can be used for model operations 74 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/models/queries.md: -------------------------------------------------------------------------------- 1 | # Queries 2 | 3 | Racer can fetch or subscribe to queries based on a model value or a database-specific query. When fetching or subscribing to a query, all of the documents associated with that query are also fetched or subscribed. 4 | 5 | > `query = model.query(collectionName, path)` 6 | > * `collectionName` The name of a collection from which to get documents 7 | > * `path` A model path whose value contains a documentId or an array of documentIds 8 | 9 | > `query = model.query(collectionName, databaseQuery)` 10 | > * `collectionName` The name of a collection from which to get documents 11 | > * `databaseQuery` A query in the database native format, such as a MonogDB query 12 | 13 | # MongoDB query format 14 | 15 | The `sharedb-mongo` adapter supports most MongoDB queries that you could pass to the Mongo `find()` method. See the [Mongo DB query documentation](https://docs.mongodb.org/manual/core/read-operations/#read-operations-query-document) and the [query selectors reference](https://docs.mongodb.org/manual/reference/operator/#query-selectors). Supported MongoDB cursor methods must be passed in as part of the query. `$sort` should be used for sorting, and skips and limits should be specified as `$skip` and `$limit`. There is no `findOne()` equivalent—use `$limit: 1` instead. 16 | 17 | Note that projections, which are used to limit the fields that a query returns, may not be defined in the query. Please refer to the [guide on using projections](https://github.com/derbyparty/derby-faq/tree/master/en#i-dont-need-all-collections-fields-in-a-browser-how-to-get-only-particular-fields-collections-projection), which you can follow if you only want specific fields of a document transferred to the browser. 18 | 19 | ## Query results 20 | 21 | After a query is subscribed or fetched, its results can be returned directly via `query.get()`. It is also possible to create a live-updating results set in the model via `query.ref()`. 22 | 23 | > `results = query.get()` 24 | > * `results` Creates and returns an array of each of the document objects matching the query 25 | 26 | > `scoped = query.ref(path)` 27 | > * `path` Local path at which to create an updating refList of the queries results 28 | > * `scoped` Returns a model scoped to the path at which results are output 29 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/models/reactive-functions.md: -------------------------------------------------------------------------------- 1 | # Reactive functions 2 | 3 | Reactive functions provide a simple way to update a computed value whenever one or more objects change. While model events respond to specific model methods and path patterns, reactive functions will be re-evaluated whenever any of their inputs or nested properties change in any way. 4 | 5 | Reactive functions may be run any number of times, so they should be [pure functions](https://en.wikipedia.org/wiki/Pure_function). In other words, they should always return the same results given the same input arguments, and they should be side effect free. By default, the inputs to the function are retrieved directly from the model, so be sure not to modify any object or array input arguments. For example, slice an array input before you sort it. The output of the model function is deep cloned by default. 6 | 7 | To execute a model function, you then call `model.start()` or `model.evaluate()`. 8 | * `evaluate()` runs a function once and returns the result. 9 | * `start()` also sets up event listeners that continually re-evaluate the 10 | * function whenever any of its input or output paths are changed. 11 | 12 | > ``` 13 | > value = model.start(path, inputPaths, [options], fn) 14 | > value = model.evaluate(inputPaths, [options], fn) 15 | > ``` 16 | > ``` 17 | > // Legacy (racer <= 0.9.5) 18 | > value = model.start(path, inputPaths..., [options], fn) 19 | > value = model.evaluate(inputPaths..., [options], fn) 20 | > ``` 21 | > 22 | > * `path` - _string | ChildModel_ - The output path at which to set the value, 23 | > keeping it updated as input paths change 24 | > * `inputPaths` - _Array_ - One or more paths whose values 25 | > will be retrieved from the model and passed to the function as inputs 26 | > * `options` - _Object_ (optional) 27 | > * `copy` - Controls automatic deep copying of the inputs and output of the 28 | > function. _Model#evaluate never deep-copies output, since the return 29 | > value is not set onto the model._ 30 | > - `'output'` (default) - Deep-copy the return value of the function 31 | > - `'input'` - Deep-copy the inputs to the function 32 | > - `'both'` - Deep-copy both inputs and output 33 | > - `'none'` - Do not automatically copy anything 34 | > * `mode` - The `model.set*` method to use when setting the output. _This has 35 | > no effect in Model#evaluate._ 36 | > - `'diffDeep'` (default) - Do a recursive deep-equal comparison on old 37 | > and new output values, attempting to issue fine-grained ops on subpaths 38 | > where possible. 39 | > - `'diff` - Do an identity comparison (`===`) on the output value, and do 40 | > a simple set if old and new outputs are different. 41 | > - `'arrayDeep'` - Compare old and new arrays item-by-item using a 42 | > deep-equal comparison for each item, issuing top-level array insert, 43 | > remove, and move ops as needed. Unlike `'diffDeep'`, this will _not_ 44 | > issue ops deep inside array items. 45 | > - `'array'` - Compare old and new arrays item-by-item using identity 46 | > comparison (`===`) for each item, issuing top-level array insert, 47 | > remove, and move ops as needed. 48 | > * `async` - _boolean_ - If true, then upon input changes, defer evaluation 49 | > of the function to the next tick, instead of immediately evaluating the 50 | > function upon each input change. _Introduced in [racer@0.9.5](https://github.com/derbyjs/racer/releases/tag/v0.9.5)._ 51 | > - This can improve UI performance when multiple inputs to a reactive 52 | > function will change in the same event loop, as `async: true` will 53 | > mean the function only needs be evaluated once instead of N times. 54 | > - _Warning:_ Avoid using `async: true` if there's any controller code 55 | > that does a `model.get()` on the output path or any paths downstream 56 | > of the output, since changes to an input path won't immediately result 57 | > in the output being updated. 58 | > * `fn` - _Function | string_ - A function or the name of a function defined 59 | > via `model.fn()` 60 | > * The function gets invoked with the values at the input paths, one input 61 | > per argument, and should return the computed output value. 62 | > * It should be a synchronous [pure function](https://en.wikipedia.org/wiki/Pure_function). 63 | > - One common side effect to avoid is `Array#sort` on an input array, since 64 | > that sorts the array in-place. If you need to do a sort, make a shallow 65 | > copy via `array.slice()` first, or use a sorting library that returns a 66 | > new array instead of sorting in-place. 67 | > - The function will be called both in Node and in the browser, so avoid 68 | > using functions whose behavior is implementation-dependent, such as the 69 | > one-argument form of [`String#localeCompare`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare). 70 | > - The function might get called with some inputs `undefined`, so be 71 | > defensive and check inputs' existence before using them. 72 | > * Return `value` - The initial value computed by the function 73 | 74 | > `model.stop(path)` 75 | > * `path` The path at which the output should no longer update. Note that the value is not deleted; it is just no longer updated 76 | 77 | In DerbyJS, `model.start()` functions should typically be established in the `init` method of a component. This method is called both before rendering on the server and then again before rendering in the browser. These reactive functions will be stopped as soon as the component is destroyed, which happens automatically when the component is removed from the page. 78 | 79 | ```js 80 | MyComponent.prototype.init = function(model) { 81 | model.start('total', 'first', 'second', function sum(x, y) { 82 | return (x || 0) + (y || 0); 83 | }); 84 | }; 85 | ``` 86 | 87 | ## Two-way reactive functions 88 | 89 | Most reactive functions define a getter only. You should treat their output as read only. In addition, it is possible to define two-way reactive functions with both a setter and a getter. Note that this is a more advanced pattern and should not be used unless you are confident that it is a strong fit for your use case. 90 | 91 | ```js 92 | // Model functions created with just a function act as getters only. 93 | // These functions update the output path when any input changes 94 | model.fn('expensiveItems', function(items) { 95 | return items && items.filter(function(item) { 96 | return item.price > 100; 97 | }); 98 | }); 99 | 100 | // It is also possible to define both a getter and a setter function 101 | // if the input values may be computed from setting the output 102 | model.fn('fullName', { 103 | // The getter function gets the current value of each of the input 104 | // arguments when any input might have changed 105 | get: function(firstName, lastName) { 106 | return firstName + ' ' + lastName; 107 | }, 108 | // The setter function is called with the value that was set at 109 | // the output path as well as the current value of the inputs. 110 | // It should return an array or object where each property is an 111 | // index that corresponds to each input argument that should be set. 112 | // If the function returns null, no items will be set. 113 | set: function(value, firstName, lastName) { 114 | return value && value.split(' '); 115 | } 116 | }); 117 | ``` 118 | 119 | ## Named functions 120 | 121 | In addition to passing in a function directly, a function can be defined on a model via a name. This name can then be used in place of a function argument. 122 | 123 | > `model.fn(name, fn)` 124 | > * `name` A name that uniquely identifies the function 125 | > * `fn` A getter function or an object with the form `{get: function(), set: function()}` 126 | 127 | Reactive functions started on the server via a name are reinitialized when the page loads. In order to add functions for use in routes as well as in the client, use the `'model'` event emitted by apps, which occurs right before an app route is called on the server and once immediately upon initialization in the client. Then, you can safely start them in the appropriate route, and they will be re-established automatically on the client. 128 | 129 | In DerbyJS, this pattern is generally less preferable to initializing model functions in a component. 130 | 131 | ```js 132 | app.on('model', function(model) { 133 | // Sort the players by score and return the top X players. The 134 | // function will automatically update the value of '_page.leaders' 135 | // as players are added and removed, their scores change, and the 136 | // cutoff value changes. 137 | model.fn('topPlayers', function(players, cutoff) { 138 | // Note that the input array is copied with slice before sorting 139 | // it. The function should not modify the values of its inputs. 140 | return players.slice().sort(function (a, b) { 141 | return a.score - b.score; 142 | }).slice(0, cutoff - 1); 143 | }); 144 | }); 145 | 146 | app.get('/leaderboard/:gameId', function(page, model, params, next) { 147 | var game = model.at('game.' + params.gameId); 148 | game.subscribe(function(err) { 149 | if (err) return next(err); 150 | game.setNull('players', [ 151 | {name: 'John', score: 4000}, 152 | {name: 'Bill', score: 600}, 153 | {name: 'Kim', score: 9000}, 154 | {name: 'Megan', score: 3000}, 155 | {name: 'Sam', score: 2000} 156 | ]); 157 | model.set('_page.cutoff', 3); 158 | model.start( 159 | // Output path 160 | '_page.topPlayers', 161 | // Input paths 162 | game.at('players'), 163 | '_page.cutoff', 164 | // Name of the function 165 | 'topPlayers' 166 | ); 167 | page.render(); 168 | }); 169 | }); 170 | ``` 171 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/models/references.md: -------------------------------------------------------------------------------- 1 | # References 2 | 3 | References make it possible to write business logic and templates that interact with the model in a general way. They redirect model operations from a reference path to the underlying data, and they set up event listeners that emit model events on both the reference and the actual object's path. 4 | 5 | References must be declared per model, since calling `model.ref` creates a number of event listeners in addition to setting a ref object in the model. When a reference is created or removed, a `change` model event is emitted. References are not actually stored in the model data, but they can be used from getter and setter methods as if they are. 6 | 7 | > `scoped = model.ref(path, to, [options])` 8 | > * `path` The location at which to create a reference. This must be underneath a [local collection](paths#local-and-remote-collections) (typically `_page`), since references must be declared per model 9 | > * `to` The location that the reference links to. This is where the data is actually stored 10 | > * `options:` 11 | > * `updateIndices` Set true to update the ref's `to` path if it contains array indices whose parents are modified via array inserts, removes, or moves 12 | > * `scoped` Returns a model scoped to the output path for convenience 13 | 14 | > `model.removeRef(path)` 15 | > * `path` The location at which to remove the reference 16 | 17 | ```js 18 | model.set('colors', { 19 | red: {hex: '#f00'} 20 | , green: {hex: '#0f0'} 21 | , blue: {hex: '#00f'} 22 | }); 23 | 24 | // Getting a reference returns the referenced data 25 | model.ref('_page.green', 'colors.green'); 26 | // Logs {hex: '#0f0'} 27 | console.log(model.get('_page.green')); 28 | 29 | // Setting a property of the reference path modifies 30 | // the underlying data 31 | model.set('_page.green.rgb', [0, 255, 0]); 32 | // Logs {hex: '#0f0', rgb: [0, 255, 0]} 33 | console.log(model.get('colors.green')); 34 | 35 | // Removing the reference has no effect on the underlying data 36 | model.removeRef('_page.green'); 37 | // Logs undefined 38 | console.log(model.get('_page.green')); 39 | // Logs {hex: '#0f0', rgb: [0, 255, 0]} 40 | console.log(model.get('colors.green')); 41 | ``` 42 | 43 | Racer also supports a special reference type created via `model.refList`. This type of reference is useful when a number of objects need to be rendered or manipulated as a list even though they are stored as properties of another object. This is also the type of reference created by a query. A reference list supports the same mutator methods as an array, so it can be bound in a view template just like an array. 44 | 45 | > `scoped = model.refList(path, collection, ids, [options])` 46 | > * `path` The location at which to create a reference list. This must be underneath a [local collection](paths#local-and-remote-collections) (typically `_page`), since references must be declared per model 47 | > * `collection` The path of an object that has properties to be mapped onto an array. Each property must be an object with a unique `id` property of the same value 48 | > * `ids` A path whose value is an array of ids that map the `collection` object's properties to a given order 49 | > * `options:` 50 | > * `deleteRemoved` Set true to delete objects from the `collection` path if the corresponding item is removed from the refList's output path 51 | > * `scoped` Returns a model scoped to the output path for convenience 52 | 53 | > `model.removeRefList(path)` 54 | > * `path` The location at which to remove the reference 55 | 56 | Note that if objects are inserted into a refList without an `id` property, a unique id from [`model.id()`](#guids) will be automatically added to the object. 57 | 58 | ```js 59 | // refLists should consist of objects with an id matching 60 | // their property on their parent 61 | model.setEach('colors', { 62 | red: {hex: '#f00', id: 'red'}, 63 | green: {hex: '#0f0', id: 'green'}, 64 | blue: {hex: '#00f', id: 'blue'} 65 | }); 66 | model.set('_page.colorIds', ['blue', 'red']); 67 | model.refList('_page.myColors', 'colors', '_page.colorIds'); 68 | 69 | model.push('_page.myColors', {hex: '#ff0', id: 'yellow'}); 70 | 71 | // Logs: [ 72 | // {hex: '#00f', id: 'blue'}, 73 | // {hex: '#f00', id: 'red'}, 74 | // {hex: '#ff0', id: 'yellow'} 75 | // ] 76 | console.log(model.get('_page.myColors')); 77 | ``` 78 | 79 | When a collection is cleaned up by `model.destroy()`, the `model.removeAllRefs()` method is invoked to remove all refs and refLists underneath the collection. 80 | 81 | > `model.removeAllRefs(from)` 82 | > * `from` Path underneath which to remove all refs and refLists 83 | 84 | It isn't neccessary to manually dereference model paths, but for debugging, testing, or special cases there is a `model.dereference()` method. 85 | 86 | > `resolved = model.dereference(from)` 87 | > * `from` Path to dereference 88 | > * `resolved` Returns the fully dereferenced path, possibly passing through multiple refs or refLists. Will return the input path if no references are found 89 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/models/setters.md: -------------------------------------------------------------------------------- 1 | # Setters 2 | 3 | Models allow getting and setting to nested undefined paths. Setting such a path first sets each undefined or null parent to an empty object or empty array. Whether or not a path segment is a number determines whether the implied parent is created as an object or array. 4 | 5 | ```js 6 | model.set('cars.DeLorean.DMC12.color', 'silver'); 7 | // Logs: { cars: { DeLorean: { DMC12: { color: 'silver' }}}} 8 | console.log(model.get()); 9 | ``` 10 | 11 | All model mutators modify data and emit events synchronously. This is only safe to do, because all remote data is synchronized with Operational Transformation, and every client will eventually see a consistent view of the same data. With a more naive approach to syncing data to the server and other clients, updates to the data would need to wait until they were confirmed successful from the server. 12 | 13 | As well as a synchronous interface, model mutators have an optional callback with an error argument `callback(err)`. This callback is called when the operation is confirmed from the server, which may be desired to confirm that data was saved before updating the UI in some rare cases. This callback should be used very rarely in practice, and data updates should be treated as sychronous, so that the UI responds immediately even if a user has a high latency connection or is currently disconnected. 14 | 15 | ## General methods 16 | 17 | > `previous = model.set(path, value, [callback])` 18 | > * `path` Model path to set 19 | > * `value` Value to assign 20 | > * `previous` Returns the value that was set at the path previously 21 | > * `callback` *(optional)* Invoked upon completion of a successful or failed operation 22 | 23 | > `obj = model.del(path, [callback])` 24 | > * `path` Model path of object to delete 25 | > * `obj` Returns the deleted object 26 | 27 | > `obj = model.setNull(path, value, [callback])` 28 | > * `path` Model path to set 29 | > * `value` Value to assign only if the path is null or undefined 30 | > * `obj` Returns the object at the path if it is not null or undefined. Otherwise, returns the new value 31 | 32 | > `previous = model.setDiff(path, value, [callback])` 33 | > * `path` Model path to set 34 | > * `value` Value to be set if not strictly equal to the current value 35 | > * `previous` Returns the value that was set at the path previously 36 | 37 | > `model.setDiffDeep(path, value, [callback])` 38 | > `model.setArrayDiff(path, value, [callback])` 39 | > `model.setArrayDiffDeep(path, value, [callback])` 40 | > * `path` Model path to set 41 | > * `value` Value to be set if different. Deep methods will do a deep traversal and deep equality check. Array methods should be used when diffing two different arrays and only inserts, removes, and moves at the top level are desired. `setDiffDeep` can diff arrays as well but may produce a set on a particular property within an array. These methods may end up performing zero or many mutations. 42 | 43 | ## Object methods 44 | 45 | > `id = model.add(path, object, [callback])` 46 | > * `path` Model path to set 47 | > * `object` A document to add. If the document has an `id` property, it will be set at that value underneath the path. Otherwise, an id from `model.id()` will be set on the object first 48 | > * `id` Returns `object.id` 49 | 50 | > `model.setEach(path, object, [callback])` 51 | > * `path` Model path underneath which each property will be set 52 | > * `object` An object whose properties are each set individually 53 | 54 | ## Number methods 55 | 56 | > `number = model.increment(path, [byNumber], [callback])` 57 | > * `path` Model path to set 58 | > * `byNumber` *(optional)* Number specifying amount to increment or decrement if negative. Defaults to 1 59 | > * `number` Returns the new value that was set after incrementing 60 | 61 | ## Array methods 62 | 63 | Array methods can only be used on paths set to arrays, null, or undefined. If the path is null or undefined, the path will first be set to an empty array before applying the method. 64 | 65 | > `length = model.push(path, value, [callback])` 66 | > * `path` Model path to an array 67 | > * `value` An item to add to the *end* of the array 68 | > * `length` Returns the length of the array with the new item added 69 | 70 | > `length = model.unshift(path, value, [callback])` 71 | > * `path` Model path to an array 72 | > * `value` An item to add to the *beginning* of the array 73 | > * `length` Returns the length of the array with the new item added 74 | 75 | > `length = model.insert(path, index, values, [callback])` 76 | > * `path` Model path to an array 77 | > * `index` Index at which to start inserting. This can also be specified by appending it to the path instead of as a separate argument 78 | > * `values` An array of items to insert at the index 79 | > * `length` Returns the length of the array with the new items added 80 | 81 | > `item = model.pop(path, [callback])` 82 | > * `path` Model path to an array 83 | > * `item` Removes the last item in the array and returns it 84 | 85 | > `item = model.shift(path, [callback])` 86 | > * `path` Model path to an array 87 | > * `item` Removes the first item in the array and returns it 88 | 89 | > `removed = model.remove(path, index, [howMany], [callback])` 90 | > * `path` Model path to an array 91 | > * `index` Index at which to start removing items 92 | > * `howMany` *(optional)* Number of items to remove. Defaults to 1 93 | > * `removed` Returns an array of removed items 94 | 95 | > `moved = model.move(path, from, to, [howMany], [callback])` 96 | > * `path` Model path to an array 97 | > * `from` Starting index of the item to move 98 | > * `to` New index where the item should be moved 99 | > * `howMany` *(optional)* Number of items to move. Defaults to 1 100 | > * `moved` Returns an arry of items that were moved 101 | 102 | ## String methods 103 | 104 | String methods can only be used on paths set to strings, null, or undefined. If the path is null or undefined, the path will first be set to an empty string before applying the method. 105 | 106 | The string methods support collaborative text editing, and Derby uses string methods to bind changes to all text inputs and textareas by default. 107 | 108 | > `model.stringInsert(path, index, text, [callback])` 109 | > * `path` Model path to a string 110 | > * `index` Character index within the string at which to insert 111 | > * `text` String to insert 112 | 113 | > `model.stringRemove(path, index, howMany, [callback])` 114 | > * `path` Model path to a string 115 | > * `index` Starting character index of the string at which to remove 116 | > * `howMany` Number of characters to remove 117 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/overview.md: -------------------------------------------------------------------------------- 1 | # DerbyJS 2 | 3 | Welcome to the documentation for DerbyJS! 4 | 5 | ## Related documentation 6 | 7 | * [ShareDB](https://github.com/share/sharedb) 8 | 9 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/views/namespaces-and-files.md: -------------------------------------------------------------------------------- 1 | # View namespaces 2 | 3 | View names have colon (`:`) separated namespaces. Lookups of views are relative to the namespace in which they are used. Thus, sub-views within components or different sections of large applications are well encapsulated and won't cause naming conflicts. 4 | 5 | ```derby 6 | 7 | ... 8 | 9 | 10 | ... 11 | 12 | 13 | 14 | 15 | ``` 16 | 17 | In addition, similar to the way that CSS allows overriding of styles by using a more specific selector, you can define views at a general namespace and then redefine them at a more specific namespace. 18 | 19 | ```derby 20 | 21 | App 22 | 23 | 24 | About - App 25 | 26 | 27 | Mission statement - App 28 | ``` 29 | 30 | ### Custom HTML tags 31 | 32 | A view can be turned into a custom HTML tag by specifying the `tag` property in it's definition. Custom tag names are global so care should be taken in their usage. 33 | 34 | ```derby 35 | 36 | 37 |
    {{data}}
    38 | 39 | 40 | 41 | 42 | ``` 43 | 44 | ## Structuring views in multiple files 45 | 46 | Views should be broken into files that correspond to major pieces of functionality, different URLs, or components. Views are included from another file with the `` tag. 47 | 48 | ```derby 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | ``` 58 | 59 | Typically, view namespaces have a one-to-one correspondence with directories and files. For example, a typical structure like: 60 | 61 | #### index.html 62 | ```derby 63 | 64 | 65 | 66 | App 67 | ``` 68 | 69 | #### about/index.html 70 | ```derby 71 | 72 | 73 | 74 | About - App 75 | ``` 76 | 77 | #### about/mission.html 78 | ```derby 79 | 80 | Mission statement - App 81 | ``` 82 | 83 | would be equivalent to: 84 | 85 | `index.html` 86 | ```derby 87 | 88 | App 89 | 90 | 91 | About - App 92 | 93 | 94 | Mission statement - App 95 | ``` 96 | 97 | Rules for importing views work the same way as [Node.js module loading](https://nodejs.org/api/modules.html) with `require()`. The `src` attribute uses the same syntax of relative paths or paths to `node_modules`. An `index.html` file can be imported via the name of the directory that it is in, just like `index.js` files in Node.js. 98 | 99 | As well, the name `index` can be used for a view that is returned for just the name of its namespace. 100 | 101 | #### index.html 102 | ```derby 103 | 104 | 105 | 106 | 107 | ``` 108 | 109 | #### home.html 110 | ```derby 111 | 112 |

    113 | 114 |

    115 | 116 | 117 | Hello! 118 | ``` 119 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/views/overview.md: -------------------------------------------------------------------------------- 1 | # Views 2 | 3 | When writing an app or new feature in Derby, you should typically start by writing its view. Derby templates can be written in HTML or Jade with [derby-jade](https://github.com/derbyparty/derby-jade). Templates define HTML/DOM output, data bindings, event listeners, and component parameters. 4 | 5 | ## Creating views 6 | 7 | Views are written in HTML files. These files are parsed and added to a Derby app with the `app.loadViews()` method. This method synchronously reads template files, traverses their includes, and calls `app.views.register()` for each view. 8 | 9 | > `app.loadViews(filename)` 10 | > * `filename` File path to root template file at which to start loading views 11 | 12 | > `app.views.register(name, source, options)` 13 | > * `name` View name to add 14 | > * `source` Derby HTML source 15 | > * `options:` 16 | > * `tag` Name of an HTML tag that will render this view 17 | > * `attributes` Space separated list of HTML tags interpreted as an attribute when directly within the view instance 18 | > * `arrays` Space separated list of HTML tags interpreted as an array of objects attribute when directly within the view instance 19 | > * `unminified` Whitespace is removed from templates by default. Set true to disable 20 | > * `string` True if the template should be interpreted as a string instead of HTML 21 | 22 | > `view = app.views.find(name, [namespace])` 23 | > * `name` View name to find 24 | > * `namespace` *(optional)* Namespace from which to start the name lookup 25 | > * `view` Returns the view template object 26 | 27 | Each view is wrapped in a tag that names it. This name must end in a colon to differentiate it from a normal HTML tag. These tags can't be nested, and they need not be closed. 28 | 29 | ```derby 30 | 31 |

    Hello, sir.

    32 | 33 | 34 |

    Howdy!

    35 | ``` 36 | 37 | is equivalent to: 38 | 39 | ```js 40 | app.views.register('serious-title', '

    Hello, sir.

    '); 41 | app.views.register('friendly-title', '

    Howdy!

    '); 42 | ``` 43 | 44 | ## Using views 45 | 46 | You can instantiate a view in a template with the `` tag, `{{view}}` expression, or by giving the view a tag name. Typically, you should use the `` tag in HTML templates. The `{{view}}` expression is useful when writing string templates or wish to include a view in an HTML attribute, script tag, or style tag. Custom tag names are global to an application. They are recommended for general purpose components, like `` or ``, but not for ordinary views. 47 | 48 | ```derby 49 | 50 |

    Hello, sir.

    51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | {{view 'serious-title'}} 59 | 60 | 61 | 62 | ``` 63 | 64 | Views may be looked up dynamically with an expression. If the view isn't found, nothing will be rendered. 65 | 66 | ```derby 67 | 68 | 69 | 70 | {{view type + '-title'}} 71 | ``` 72 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/views/template-syntax/blocks.md: -------------------------------------------------------------------------------- 1 | # Blocks 2 | 3 | Blocks are template expressions that start with special keywords. They are used to conditionally render, repeat, or control the way in which sections of a template are rendered. 4 | 5 | Similar to HTML tags, blocks end in a forward slash followed by the same keyword that started them. The closing keyword is optional but recommended for clarity. For example, both `{{with}}...{{/with}}` and `{{with}}...{{/}}` are parsed correctly. 6 | 7 | # Conditionals 8 | 9 | Conditional blocks use the `if`, `else if`, `else`, and `unless` keywords. They render the first template section that matches a condition or nothing if none match. Like in Mustache and Handlebars, zero length arrays (`[]`) are treated as falsey. Other than that, falsey values are the same as JavaScript: `false`, `undefined`, `null`, `''`, and `0`. 10 | 11 | ```derby 12 | {{if user.name}} 13 |

    user.name

    14 | {{else if user}} 15 |

    Unnamed user

    16 | {{else}} 17 | No user 18 | {{/if}} 19 | ``` 20 | 21 | The inverse of `if` is `unless`. For clarity, unless should only be used when there is no `else` condition. A block that has an unless and else condition can usually be writtern more clearly as an if and else. 22 | 23 | ```derby 24 | {{unless items}} 25 | Please add some items 26 | {{/unless}} 27 | ``` 28 | 29 | The contents of a conditional block are only re-rendered when a different condition starts to match. If the values in the conditional change, the condition expression is evaluated, but the DOM is not updated if the same section matches. 30 | 31 | # Each 32 | 33 | Each blocks repeat for each of the items in an array. They cannot iterate over objects. 34 | 35 | ```derby 36 | {{each items}} 37 |

    {{this.text}}

    38 | {{else}} 39 | No items 40 | {{/each}} 41 | ``` 42 | 43 | In addition to an alias to the array item, eaches support an alias for the index of the item. This index alias supports binding and will be updated as the array changes. 44 | 45 | ```derby 46 | {{each items as #item, #i}} 47 | {{#i + 1}}. {{#item.text}} 48 | {{/each}} 49 | ``` 50 | 51 | Derby has very granular model events to describe array mutations as inserts, removes, and moves. It maps these directly into efficient DOM mutations of just what changed. 52 | 53 | # With 54 | 55 | With blocks set the path context of a block, but they do not trigger re-rendering. Their primary use is to set an alias to a path inside of their contents. 56 | 57 | Aliases can be a convenient way to set a name that can be used throughout a section of a template or many nested views and/or components. 58 | 59 | ```derby 60 | 61 | {{with _session.user as #user}} 62 | 63 | {{/with}} 64 | 65 | {{with {name: 'Jim', age: 32} as #user}} 66 | 67 | {{/with}} 68 | 69 | 70 |

    {{#user.name}}

    71 |

    {{#user.age}}

    72 | ``` 73 | 74 | # On 75 | 76 | To clear UI state, to optimize performance by rendering larger sections, or to work around issues with template bindings not rendering often enough, an `{{on}}` block can provide more control. Its contents will re-render whenever any of its paths change. 77 | 78 | ```derby 79 | {{on #profile.id}} 80 |

    {{#profile.name}}

    81 | 82 | {{/on}} 83 | 84 | {{on first, second, third}} 85 | 86 | {{/on}} 87 | ``` 88 | 89 | # Unbound and bound 90 | 91 | Bindings are created by default for all template expressions. To render an initial value only and not create bindings, the `{{unbound}}` block may be wrapped around a template section. Bindings can be toggled back on with a `{{bound}}` block. 92 | 93 | ```derby 94 | {{unbound}} 95 | 96 | {{bound}} 97 | 98 | {{/bound}} 99 | {{/unbound}} 100 | ``` 101 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/views/template-syntax/escaping.md: -------------------------------------------------------------------------------- 1 | # Escaping 2 | 3 | Derby escapes values as required when it renders HTML. Escaping is relative to whether it is rendering inside of an HTML attribute or text. For example, Derby will escape `"` as `"` inside of an HTML attribute, and it will escape `<` as `<` inside of text. 4 | 5 | Derby's templates also follow HTML escaping rules. Derby will parse the string `{{` as the start of a template tag, so if you wish to write this value in an attribute or the text of a Derby template, you can use the HTML entity equivalent: `{{`. 6 | 7 | ## Rendering unescaped HTML 8 | 9 | The `unescaped` keyword may be used to render an HTML string without escaping. It is *very unlikely* that you should use this feature. Derby has many ways of dynamically creating views. Unescaped HTML is unsafe, is typically slower, and is rarely necessary with Derby. This feature is intended only for rendering the output of a well-tested library that produces sanitized HTML, such as [Google Caja](https://developers.google.com/caja/). 10 | 11 | ```derby 12 | 13 |
    {{unescaped rawHtml}}
    14 | ``` 15 | 16 | Instead, prefer passing in a template as an attribute or dynamically selecting a view in most cases. 17 | 18 | ```derby 19 | 20 | 21 | 22 | Custom HTML for this user! 23 | 24 | 25 | 26 |
    27 | 28 | {{@content}} 29 |
    30 | ``` 31 | 32 | ```derby 33 | 34 | 35 | ``` 36 | 37 | If you need completely dynamic generation of HTML (such as implementing an HTML or template editor in your application), it is even possible to use Derby's HTML parser and pass the returned Template object to your views. Derby will render this HTML safely without any Cross-site Scripting (XSS) concerns. You'll even be able to use Derby's template syntax! See how this is done in the [Derby render example](https://github.com/derbyjs/derby-examples/blob/master/render/index.js#L29), which powers the live template editor on the [DerbyJS home page](https://derbyjs.com/). 38 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/views/template-syntax/functions-and-events.md: -------------------------------------------------------------------------------- 1 | # Functions and events 2 | 3 | Attributes beginning with `on-` add listeners to DOM events and component events. Under the hood, events on elements are added with `element.addEventListener()` and events on components are added with `component.on()`. Adding events declaritively with attributes is easier than CSS selectors and less prone to unexpectedly breaking when refactoring templates or classes for styling. 4 | 5 | ```derby 6 | 7 | 8 | ``` 9 | 10 | ```js 11 | // Equivalent to: 12 | input.addEventListener('mousedown', function(event) { 13 | self.mousedownInput(event); 14 | }, false); 15 | input.addEventListener('blur', function(event) { 16 | self.blurInput(); 17 | self.update(); 18 | }, false); 19 | ``` 20 | 21 | ## View functions 22 | 23 | Functions are looked up on the current component's controller, the page, and the global, in that order. The majority of functions are defined on component prototypes, generic shared utility functions are defined on the page prototype, and the global provides access to functions like `new Date()` and `console.log()`. 24 | 25 | ```derby 26 | 27 | 28 | 29 | {{sum(1, 2, 4)}} 30 | 31 | {{console.log('rendering value', value)}} 32 | ``` 33 | 34 | ```js 35 | // component prototypes are where most functions are defined 36 | UserList.prototype.delUser = function(userId) { 37 | this.users.del(userId); 38 | }; 39 | 40 | // app.proto is the prototype for all pages created by the app 41 | app.proto.sum = function() { 42 | var sum = 0; 43 | for (var i = 0; i < arguments.length; i++) { 44 | sum += arguments[i]; 45 | } 46 | return sum; 47 | }; 48 | ``` 49 | 50 | ### Component events 51 | Components support custom events. Dashes are transformed into camelCase. 52 | 53 | See the [component events](components/events) documentation for more detail on using events and component functions. 54 | ```derby 55 | 56 | ``` 57 | 58 | ```js 59 | // Equivalent to: 60 | modal.on('close', function() { 61 | self.reset(); 62 | }); 63 | modal.on('fullView', function() { 64 | back.fade(); 65 | }); 66 | ``` 67 | 68 | 69 | ### Special HTML rules 70 | 71 | As a convenience, an `on-click` listener can be added to a link without an `href`. Derby will add an `href="#"` and prevent the default action automatically if no href is specified. 72 | 73 | ```derby 74 | 75 | Hi 76 | ``` 77 | 78 | HTML forms have very useful behavior, but their default action on submit will navigate away from the current page. If an `on-submit` handler is added to a form with no `action` attribute, the default will be prevented. 79 | 80 | ```derby 81 |
    82 | 83 | 84 | 85 | 86 | 87 |
    88 | ``` 89 | 90 | ### DOM event arguments 91 | 92 | For functions invoked by DOM events only, the special arguments `$event` or `$element` may be specified. The `$event` argument is the DOM Event object passed to the listener function for `addEventListener()`. The `$element` argument is a reference to the element on which the listener attribute is specified. These arguments are only passed to functions if explicitly specified. 93 | 94 | ```derby 95 | 96 | 97 | {{each rows as #row}} 98 | 99 | 100 | 101 | 102 | {{/each}} 103 | 104 |
    {{#row.name}}{{#row.description}}
    105 | ``` 106 | 107 | ```js 108 | UserList.prototype.clickRow = function(e, tr) { 109 | // Ignore clicks on or in links 110 | var node = e.target; 111 | while (node && node !== tr) { 112 | if (node.tagName === 'A') return; 113 | node = node.parentNode; 114 | } 115 | // Cancel the original click event inside of the row 116 | e.stopPropagation(); 117 | // Pretend like the click happened on the first link in the row 118 | var event = new MouseEvent('click', e); 119 | var link = tr.querySelector('a'); 120 | if (link) link.dispatchEvent(event); 121 | }; 122 | ``` 123 | 124 | ## Scoped model arguments 125 | 126 | Functions can be passed the value of any view path. In some cases, it can be convenient to get a [scoped model](../../models/paths#scoped-models) to the view name instead. To pass a scoped model, you can wrap the view path in `$at()`. Instead of getting the value for a view path, this will return a scoped model. It will return undefined if no scoped model can be created for a view path. 127 | 128 | ```derby 129 | 130 | ``` 131 | 132 | ```js 133 | app.proto.toggle = function(scoped) { 134 | scoped.set(!scoped.get()); 135 | }; 136 | ``` -------------------------------------------------------------------------------- /md/docs/derby-0.10/views/template-syntax/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/views/template-syntax/literals.md: -------------------------------------------------------------------------------- 1 | # Literals 2 | 3 | Derby supports creating JavaScript literals in templates. The syntax is identical to JavaScript, except that identifiers within literals are parsed as [view paths](paths) instead of JavaScript variables. Derby parses template expressions with Esprima, so its coverage of JavaScript expression syntax is comprehensive. 4 | 5 | ## Simple literals 6 | 7 | ```derby 8 | 9 | {{0}} 10 | {{1.1e3}} 11 | {{0xff}} 12 | 13 | {{true}} 14 | {{false}} 15 | 16 | {{'Hi'}} 17 | {{"Hey"}} 18 | 19 | {{/([0-9])+/}} 20 | 21 | {{null}} 22 | 23 | {{undefined}} 24 | 25 | {{ [0, 1, 2] }} 26 | 27 | {{ {name: 'Jim'} }} 28 | ``` 29 | 30 | For greater efficiency, simple literals are instantiated at the time of parsing. Object literals created at parse time will be passed by reference to controller functions, so be careful not to modify them. 31 | 32 | ```derby 33 | 34 | 35 | ``` 36 | 37 | It is possible to iterate over object literals in template expressions. In most cases, it makes more sense to define constants in the controller or use HTML, but this can be handy when prototyping and debugging. 38 | 39 | ```derby 40 |
      41 | {{each ['A', 'B', 'C'] as #letter}} 42 |
    • {{#letter}}
    • 43 | {{/each}} 44 |
    45 | 46 | {{with {name: 'Jim', age: 23} as #user}} 47 |

    {{#user.name}}

    48 |

    {{#user.age}}

    49 | {{/with}} 50 | ``` 51 | 52 | ## Literals containing paths 53 | 54 | Literals containing paths are created at render time and populated with the appropriate values from the model. 55 | 56 | ```derby 57 |
      58 | {{each [first, 1, 2, 3] as #item}} 59 |
    • {{#item}}
    • 60 | {{/each}} 61 |
    62 | 63 | 64 | ``` 65 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/views/template-syntax/operators.md: -------------------------------------------------------------------------------- 1 | # Operators 2 | 3 | All non-assigment JavaScript operators and using parentheses for grouping expressions are supported. They work exactly the same as they do in Javascript. Operators that do an assignment, such as the `++` increment operator, are not supported. This avoids rendering having side effects. 4 | 5 | ```derby 6 | 7 | {{-value}} 8 | 9 | {{+value}} 10 | 11 | 12 | {{left + right}} 13 | 14 | {{left - right}} 15 | 16 | {{left * right}} 17 | 18 | {{left / right}} 19 | 20 | {{left % right}} 21 | 22 | 23 | {{!value}} 24 | 25 | {{left || right}} 26 | 27 | {{left && right}} 28 | 29 | 30 | {{~value}} 31 | 32 | {{left | right}} 33 | 34 | {{left & right}} 35 | 36 | {{left ^ right}} 37 | 38 | 39 | {{left << right}} 40 | 41 | {{left >> right}} 42 | 43 | {{left >>> right}} 44 | 45 | 46 | {{left === right}} 47 | 48 | {{left !== right}} 49 | 50 | {{left == right}} 51 | 52 | {{left != right}} 53 | 54 | 55 | {{left < right}} 56 | 57 | {{left > right}} 58 | 59 | {{left <= right}} 60 | 61 | {{left >= right}} 62 | 63 | 64 | {{typeof value}} 65 | 66 | {{left instanceof right}} 67 | 68 | {{left in right}} 69 | 70 | 71 | {{test ? consequent : alternate}} 72 | 73 | 74 | {{a, b, c, d}} 75 | ``` 76 | 77 | ## Two-way operators 78 | 79 | In addition to getting values, operators for which there is a well defined opposite support two-way data bindings. These setters will make the relationship consistent with the value that is set. 80 | 81 | ```derby 82 | 83 | 86 | 87 | 92 | {{each options as #option}} 93 | 96 | {{/each}} 97 | ``` 98 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/views/template-syntax/overview.md: -------------------------------------------------------------------------------- 1 | # Template syntax 2 | 3 | Derby’s template syntax is loosely based on [Handlebars](https://handlebarsjs.com/), a semantic templating language that extends [Mustache](https://mustache.github.io/mustache.5.html). 4 | 5 | Semantic templates encourage separation of logic from presentation. Instead of arbitrary code, there are a small set of template expressions. During rendering, data are passed to the template, and template expressions are replaced with the appropriate values. In Derby, this data comes from the model. 6 | 7 | Derby supports calling controller functions and the full JavaScript expression syntax. Expressions are parsed with [Esprima](http://esprima.org/) and do not use JavaScript's `eval()` or `new Function()`. 8 | 9 | ## HTML 10 | 11 | With the exception of templates that return strings and the contents of ` 65 | ``` 66 | 67 | HTML comments are removed, except for those with square brackets immediately inside of them. This syntax is used by Internet Explorer's conditional comments. It can also be used to include comments for copyright notices or the like. 68 | 69 | ```derby 70 | 71 | 72 | 73 | 74 | ``` 75 | -------------------------------------------------------------------------------- /md/docs/derby-0.10/views/template-syntax/paths.md: -------------------------------------------------------------------------------- 1 | # Paths 2 | 3 | Template paths use JavaScript syntax with a few small modifications. 4 | 5 | ## Model values 6 | 7 | What would be identifiers for variable names in JavaScript get a value from the model and bind to any updates. If the path returns null or undefined, nothing is rendered. 8 | 9 | Examples of rendering model values: 10 | 11 | ```derby 12 | {{user.name}} 13 | 14 | {{user.bestFriends[0].name}} 15 | 16 | {{users[userId].name}} 17 | ``` 18 | 19 | ```js 20 | model.get('user.name'); 21 | 22 | model.get('user.bestFriends.0.name'); 23 | 24 | var userId = model.get('userId'); 25 | model.get('users.' + userId + '.name'); 26 | ``` 27 | 28 | ## Attributes 29 | 30 | Values are passed into views with attributes. Within the view, these values are accessed via paths that start with an at sign (`@`). In addition, there is an `@content` attribute created for any content inside of a view tag. 31 | 32 | ```derby 33 | 34 | 38 | 39 | 40 |
  • 41 | {{if $render.url === @href}} 42 | {{@content}} 43 | {{else}} 44 | {{@content}} 45 | {{/if}} 46 |
  • 47 | ``` 48 | 49 | See [View attributes](view-attributes) for additional detail on passing data to views. 50 | 51 | ## Aliases 52 | 53 | Aliases label path expressions. They must begin with a hash (`#`) character to make it more obvious whether a path is an alias or a model value. Each of the block types support defining aliases with the `as` keyword. 54 | 55 | Aliases make it possible to refer to the scope of the current block or a parent block. 56 | 57 | ```derby 58 | {{with user as #user}} 59 |

    {{#user.name}}

    60 |

    {{#user.headline}}

    61 | {{if #user.friendList as #friendList}} 62 | 63 |

    Friends of {{#user.name}}

    64 |
      65 | {{each #friendList as #friend}} 66 |
    • {{#friend.name}}
    • 67 | {{/each}} 68 |
    69 | {{/if}} 70 | {{/with}} 71 | ``` 72 | 73 | ## Relative paths - DEPRECATED 74 | 75 | Relative view paths begin with `this`. They refer to the expression in the containing block. 76 | 77 | Aliases are preferred to relative paths, as they are more clear. Relative paths came from implementing a syntax inspired by Handlebars, but Derby has been moving toward increased consistency with JavaScript, and the alternate use of the keyword `this` is confusing. Expect that this feature will be removed in a future version of Derby. 78 | 79 | ```derby 80 | {{with user}} 81 |

    {{this.name}}

    82 |

    {{this.headline}}

    83 | {{if this.friendList}} 84 |

    Friends

    85 |
      86 | {{each this}} 87 |
    • {{this.name}}
    • 88 | {{/each}} 89 |
    90 | {{/if}} 91 | {{/with}} 92 | ``` 93 | -------------------------------------------------------------------------------- /md/docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /md/faq.md: -------------------------------------------------------------------------------- 1 | ### What is DerbyJS? 2 | 3 | DerbyJS is a framework for developing web applications, making it much easier to build real-time collaborative functionality. 4 | 5 | Built from a collection of standard Node.js modules, DerbyJS can be brought into existing projects, or existing node modules can easily be brought into a DerbyJS project. 6 | 7 | Derby provides server-side rendering through it's HTML-like templating language. Templates can be organized into components for better code reuse and faster application development. 8 | Templates and components are backed by a real-time data model called Racer. 9 | 10 | 11 | ### What is Racer? 12 | 13 | Racer is the data manipulation layer in Derby, it wraps the [ShareDB](https://github.com/share/sharedb) library. Racer provides a consistent API for managing data on the server and client, and seamlessly syncs the two with little thought from the user. 14 | 15 | 16 | ### How does Racer compare to Firebase? 17 | 18 | Racer provides a lot of the same functionality as Firebase. Besides the obvious tradeoffs between open source and proprietary (supported) software, Racer has more granular update information, while Firebase has more mature solutions around authentication and documentation. 19 | 20 | 21 | ### What is a Racer Model? 22 | 23 | The Model is a JavaScript object that provides an api for real-time data. All data manipulations are done through it, and the model is synced on both the server and the client. Models are created on server for each request and also you can create any number of Models directly from a Store anytime, but there is only one Model on client. 24 | 25 | 26 | ### What is a Racer Store? 27 | 28 | Store is singular JavaScript object on the server for managing database connections. All Models are created from Store. If a Model is on the client, a connection to the store is established by racer-browserchannel. The Store is wrapper on [ShareJS](http://sharejs.org) and a place where all OT conflict resolution magic is happen. 29 | 30 | ### What is OT? 31 | 32 | OT - [Operational Transformation](https://en.wikipedia.org/wiki/Operational_transformation) - is a conflict resolution technique used in [ShareJS](http://sharejs.org). The main idea is to resolve conflicts created by multiple clients trying to modify the same data in a distributed system. 33 | OT is different for different data types. ShareJS uses a json data type in Racer. There is also a text data type in ShareJS and plans to add a rich text data type. 34 | Using Derby you can treat OT like black box with some magic. ShareJS will merge operations for arrays, strings, number increments 'out of the box', but if two clients make set operation for same data at same time (data with same version) there is no way to capture this correcly and one of two operations will be lost. If it is critical (and you have a big head), you can create application specific data types for ShareJS. 35 | 36 | ### Why do we need Mongo and Redis? 37 | 38 | Derby, and hence Racer, are powered by [ShareDB](https://github.com/share/sharedb). ShareDB currently only has a production ready adapter for Mongo, and optionally uses Redis to enable scaling past a single node process. More backends can be added to ShareDB by writing adapters, see [sharedb-mongo](https://github.com/share/sharedb-mongo) for example. 39 | 40 | 41 | ### How can I use web sockets with Racer instead of long polling? 42 | 43 | Use [racer-ws](https://github.com/derbyparty/racer-ws) or [racer-highway](https://github.com/derbyparty/racer-highway) module. 44 | 45 | ### What is fetch and subscribe? 46 | 47 | A newly created Model has no data in it. Before you can use any data from the database, you need to bring it into Model. There are two methods: fetch - simply queries the database and loads it into the Model, subscribe - similarly queries the database and loads the data into the Model, but if this data changes in the database, data in your Model will be updated. You can fetch/subscribe whole collections, particular documents, or even just one field in a document. 48 | 49 | 50 | ### What if JavaScript is disabled? 51 | 52 | If js is disabled in browser, client app router triggers on server for every url change. In this case site looks more like static one. 53 | Search engine spiders also get html which is good for SEO. 54 | 55 | ### Can I use Derby in Phonegap? 56 | 57 | Starting from version 0.6 - yes, you can. 58 | 59 | 60 | ### What if I have more questions? 61 | 62 | These are just a few of the frequently asked questions. This is a work in progress. If your question is not answered here, please search over on the [Google Group](https://groups.google.com/forum/?fromgroups#!forum/derbyjs). If you don't see an existing question, then let us know what is wrong. Please provide any related code snippets. 63 | 64 | ------ 65 | -------------------------------------------------------------------------------- /md/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /md/resources.md: -------------------------------------------------------------------------------- 1 | ## Community 2 | 3 | [Github Issues](https://github.com/derbyjs/derby/issues) 4 | Submit a problem 5 | 6 | 7 | ### Modules 8 | 9 | [generator-derby](https://github.com/derbyparty/generator-derby) 10 | Yeoman generator that scaffolds out a Derby 0.6 app 11 | 12 | [derby-login](https://github.com/derbyparty/derby-login) 13 | Auth 14 | 15 | [racer-schema](https://github.com/derbyparty/racer-schema) 16 | Schema Validation 17 | 18 | [racer-ws](https://github.com/derbyparty/racer-ws) 19 | Web Sockets 20 | 21 | [racer-highway](https://github.com/derbyparty/racer-highway) 22 | Web Sockets which downgrades to Browser Channel 23 | 24 | [derby-hook](https://github.com/derbyparty/derby-hook) 25 | After submit hook wrapper 26 | 27 | [derby-i18n](https://github.com/jamesknelson/derby-i18n) 28 | Simple filesystem-based i18n support for derby 29 | 30 | 31 | ### Compilers 32 | 33 | [derby-jade](https://github.com/derbyparty/derby-jade) 34 | Jade 35 | 36 | [derby-less](https://github.com/derbyjs/derby-less) 37 | Less 38 | 39 | [derby-markdown](https://github.com/derbyparty/derby-markdown) 40 | Markdown 41 | 42 | [derby-stylus](https://github.com/derbyjs/derby-stylus) 43 | Stylus 44 | 45 | 46 | ## Sources 47 | 48 | [DerbyJS Github](https://github.com/derbyjs) 49 | Derby, Racer, etc sources 50 | 51 | [Share Github](https://github.com/share) 52 | ShareDB, etc sources 53 | 54 | [Derbyparty Github](https://github.com/derbyparty) 55 | Modules from Community 56 | 57 | [Derby Examples](https://github.com/derbyjs/derby-examples) 58 | Example applications 59 | 60 | [Derby Site](https://github.com/derbyjs/derby-site) 61 | This site 62 | 63 | 64 | ## Component demos 65 | 66 | Derby components from [ile](https://github.com/ile/) 67 | 68 | 69 | ## Projects in production 70 | 71 | [Lever](https://lever.co/) 72 | A modern web app for enterprise hiring 73 | 74 | [Type4Fame](http://type4fame.com/) 75 | Multiplayer Typing Game - [source code](https://github.com/cray0000/type4fame) 76 | 77 | Realtime forum for [bombermine](http://bombermine.com/) players 78 | 79 | All-in-one productivity tool [Aamu.app](https://aamu.app/) made by [ile](https://github.com/ile) 80 | -------------------------------------------------------------------------------- /md/started.md: -------------------------------------------------------------------------------- 1 | ## Getting started 2 | 3 |

    4 | In order to get started writing your first DerbyJS app, you need to have Node.js and MongoDB installed. While DerbyJS is written to support any database, the MongoDB adapter is the most complete. If you don't have those, get setup! 5 |

    6 | 7 | --- 8 | 9 | ## Run examples 10 | 11 | There are several examples in the [derby-examples](https://github.com/derbyjs/derby-examples) repository. 12 | You can clone the repo and install: 13 | ```bash 14 | git clone https://github.com/derbyjs/derby-examples.git 15 | cd derby-examples 16 | npm install 17 | ``` 18 | 19 | You can run each of the examples from their own directories: 20 | 21 | ```bash 22 | cd ~/derby-examples/directory 23 | node server.js 24 | ``` 25 | 26 | The examples written in CoffeeScript are meant to be run via the coffee command: 27 | 28 | ```bash 29 | npm install -g coffeescript 30 | cd ~/derby-examples/sink 31 | coffee server.coffee 32 | ``` 33 | 34 | 53 | 54 | --- 55 | 56 | ## Reference 57 | 58 | Be sure to check out the [Docs](docs) to get a deeper understanding of how DerbyJS makes developing apps easier! 59 | See more links in the [Resources](resources) section. 60 | 61 | --- 62 | 63 | ## Environment 64 | 65 | For Derby you need: 66 | * [Node.js](https://nodejs.org) 67 | * [MongoDB](https://docs.mongodb.com/manual/administration/install-community/) 68 | * [Redis](https://redis.io/) is optional. It can be used to scale pub/sub beyond one server process 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "derby-site", 3 | "version": "0.0.4", 4 | "description": "derby-site", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "compose-up": "SWARM_MODE=false docker-compose -f ./deploy/docker-compose.yaml up -d && exit 0", 10 | "compose-stop": "SWARM_MODE=false docker-compose -f ./deploy/docker-compose.yaml stop && exit 0", 11 | "compose-down": "SWARM_MODE=false docker-compose -f ./deploy/docker-compose.yaml down && exit 0", 12 | "deploy": "SWARM_MODE=true docker stack deploy --compose-file ./deploy/docker-compose.yaml derbyjs && exit 0" 13 | }, 14 | "dependencies": { 15 | "derby": "^0.10.15", 16 | "derby-stylus": "^0.3.0", 17 | "express": "^4.17.1", 18 | "glob": "^7.1.2", 19 | "highlight.js": "^9.11.0", 20 | "marked": "^0.7.0", 21 | "serve-favicon": "^2.5.0" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git://github.com/derbyjs/derby-site.git" 26 | }, 27 | "keywords": [ 28 | "derby" 29 | ], 30 | "author": "Vladimir Makhaev", 31 | "contributors": [ 32 | "Eric Samatov " 33 | ], 34 | "bugs": { 35 | "url": "https://github.com/derbyjs/derby-site/issues" 36 | }, 37 | "license": "MIT", 38 | "homepage": "https://github.com/derbyjs/derby-site#readme" 39 | } 40 | -------------------------------------------------------------------------------- /public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/.gitkeep -------------------------------------------------------------------------------- /public/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.1.1 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | .btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn:active,.btn.active{background-image:none}.btn-default{background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;text-shadow:0 1px 0 #fff;border-color:#ccc}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-primary{background-image:-webkit-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:linear-gradient(to bottom,#428bca 0,#2d6ca2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#2b669a}.btn-primary:hover,.btn-primary:focus{background-color:#2d6ca2;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#2d6ca2;border-color:#2b669a}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-color:#e8e8e8}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-color:#357ebd}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f3f3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#222 0,#282828 100%);background-image:linear-gradient(to bottom,#222 0,#282828 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0)}.progress-bar{background-image:-webkit-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0)}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0)}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0)}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0)}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);border-color:#3278b3}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0)}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} -------------------------------------------------------------------------------- /public/css/github.min.css: -------------------------------------------------------------------------------- 1 | pre code{display:block;padding:.5em;color:#333;background:#f8f8ff}pre .comment,pre .template_comment,pre .diff .header,pre .javadoc{color:#998;font-style:italic}pre .keyword,pre .css .rule .keyword,pre .winutils,pre .javascript .title,pre .nginx .title,pre .subst,pre .request,pre .status{color:#333;font-weight:bold}pre .number,pre .hexcolor,pre .ruby .constant{color:#099}pre .string,pre .tag .value,pre .phpdoc,pre .tex .formula{color:#d14}pre .title,pre .id,pre .coffeescript .params,pre .scss .preprocessor{color:#900;font-weight:bold}pre .javascript .title,pre .lisp .title,pre .clojure .title,pre .subst{font-weight:normal}pre .class .title,pre .haskell .type,pre .vhdl .literal,pre .tex .command{color:#458;font-weight:bold}pre .tag,pre .tag .title,pre .rules .property,pre .django .tag .keyword{color:#000080;font-weight:normal}pre .attribute,pre .variable,pre .lisp .body{color:#008080}pre .regexp{color:#009926}pre .class{color:#458;font-weight:bold}pre .symbol,pre .ruby .symbol .string,pre .lisp .keyword,pre .tex .special,pre .prompt{color:#990073}pre .built_in,pre .lisp .title,pre .clojure .built_in{color:#0086b3}pre .preprocessor,pre .pragma,pre .pi,pre .doctype,pre .shebang,pre .cdata{color:#999;font-weight:bold}pre .deletion{background:#fdd}pre .addition{background:#dfd}pre .diff .change{background:#0086b3}pre .chunk{color:#aaa} -------------------------------------------------------------------------------- /public/css/rainbow.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Style with support for rainbow parens 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; padding: 0.5em; 9 | background: #474949; color: #D1D9E1; 10 | } 11 | 12 | 13 | .hljs-body, 14 | .hljs-collection { 15 | color: #D1D9E1; 16 | } 17 | 18 | .hljs-comment, 19 | .hljs-template_comment, 20 | .diff .hljs-header, 21 | .hljs-doctype, 22 | .lisp .hljs-string, 23 | .hljs-javadoc { 24 | color: #969896; 25 | font-style: italic; 26 | } 27 | 28 | .hljs-keyword, 29 | .clojure .hljs-attribute, 30 | .hljs-winutils, 31 | .javascript .hljs-title, 32 | .hljs-addition, 33 | .css .hljs-tag { 34 | color: #cc99cc; 35 | } 36 | 37 | .hljs-number { color: #f99157; } 38 | 39 | .hljs-command, 40 | .hljs-string, 41 | .hljs-tag .hljs-value, 42 | .hljs-phpdoc, 43 | .tex .hljs-formula, 44 | .hljs-regexp, 45 | .hljs-hexcolor { 46 | color: #8abeb7; 47 | } 48 | 49 | .hljs-title, 50 | .hljs-localvars, 51 | .hljs-function .hljs-title, 52 | .hljs-chunk, 53 | .hljs-decorator, 54 | .hljs-built_in, 55 | .lisp .hljs-title, 56 | .hljs-identifier 57 | { 58 | color: #b5bd68; 59 | } 60 | 61 | .hljs-class .hljs-keyword 62 | { 63 | color: #f2777a; 64 | } 65 | 66 | .hljs-variable, 67 | .lisp .hljs-body, 68 | .smalltalk .hljs-number, 69 | .hljs-constant, 70 | .hljs-class .hljs-title, 71 | .hljs-parent, 72 | .haskell .hljs-label, 73 | .hljs-id, 74 | .lisp .hljs-title, 75 | .clojure .hljs-title .hljs-built_in { 76 | color: #ffcc66; 77 | } 78 | 79 | .hljs-tag .hljs-title, 80 | .hljs-rules .hljs-property, 81 | .django .hljs-tag .hljs-keyword, 82 | .clojure .hljs-title .hljs-built_in { 83 | font-weight: bold; 84 | } 85 | 86 | .hljs-attribute, 87 | .clojure .hljs-title { 88 | color: #81a2be; 89 | } 90 | 91 | .hljs-preprocessor, 92 | .hljs-pragma, 93 | .hljs-pi, 94 | .hljs-shebang, 95 | .hljs-symbol, 96 | .hljs-symbol .hljs-string, 97 | .diff .hljs-change, 98 | .hljs-special, 99 | .hljs-attr_selector, 100 | .hljs-important, 101 | .hljs-subst, 102 | .hljs-cdata { 103 | color: #f99157; 104 | } 105 | 106 | .hljs-deletion { 107 | color: #dc322f; 108 | } 109 | 110 | .tex .hljs-formula { 111 | background: #eee8d5; 112 | } -------------------------------------------------------------------------------- /public/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /public/images/arrows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/arrows.png -------------------------------------------------------------------------------- /public/images/book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/book.png -------------------------------------------------------------------------------- /public/images/browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/browser.png -------------------------------------------------------------------------------- /public/images/comments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/comments.png -------------------------------------------------------------------------------- /public/images/derby-medium-cream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/derby-medium-cream.png -------------------------------------------------------------------------------- /public/images/derby-medium-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/derby-medium-transparent.png -------------------------------------------------------------------------------- /public/images/derby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/derby.png -------------------------------------------------------------------------------- /public/images/derbyparty.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/derbyparty.jpg -------------------------------------------------------------------------------- /public/images/docs/charts-debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/docs/charts-debug.png -------------------------------------------------------------------------------- /public/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/favicon.ico -------------------------------------------------------------------------------- /public/images/flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/flag.png -------------------------------------------------------------------------------- /public/images/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/github.png -------------------------------------------------------------------------------- /public/images/hourse-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/hourse-logo.png -------------------------------------------------------------------------------- /public/images/html5logos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/html5logos.png -------------------------------------------------------------------------------- /public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/logo.png -------------------------------------------------------------------------------- /public/images/love.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/love.png -------------------------------------------------------------------------------- /public/images/mail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/mail.png -------------------------------------------------------------------------------- /public/images/nodejs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/nodejs.png -------------------------------------------------------------------------------- /public/images/octocat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/octocat.png -------------------------------------------------------------------------------- /public/images/octocat30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/octocat30.png -------------------------------------------------------------------------------- /public/images/responsive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/responsive.png -------------------------------------------------------------------------------- /public/images/ticket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/ticket.png -------------------------------------------------------------------------------- /public/images/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derbyjs/derby-site/2c8695d8544c459d249a4f6bdef64868cd4e4936/public/images/user.png -------------------------------------------------------------------------------- /public/js/app.js: -------------------------------------------------------------------------------- 1 | // NOTICE!! DO NOT USE ANY OF THIS JAVASCRIPT 2 | // IT'S ALL JUST JUNK FOR OUR DOCS! 3 | // ++++++++++++++++++++++++++++++++++++++++++ 4 | 5 | /*! 6 | * Copyright 2013 Twitter, Inc. 7 | * 8 | * Licensed under the Creative Commons Attribution 3.0 Unported License. For 9 | * details, see http://creativecommons.org/licenses/by/3.0/. 10 | */ 11 | 12 | 13 | !function ($) { 14 | 15 | $(function(){ 16 | 17 | // IE10 viewport hack for Surface/desktop Windows 8 bug 18 | // 19 | // See Getting Started docs for more information 20 | if (navigator.userAgent.match(/IEMobile\/10\.0/)) { 21 | var msViewportStyle = document.createElement("style"); 22 | msViewportStyle.appendChild( 23 | document.createTextNode( 24 | "@-ms-viewport{width:auto!important}" 25 | ) 26 | ); 27 | document.getElementsByTagName("head")[0]. 28 | appendChild(msViewportStyle); 29 | } 30 | 31 | 32 | var $window = $(window) 33 | var $body = $(document.body) 34 | 35 | var navHeight = $('.navbar').outerHeight(true) + 10 36 | 37 | $body.scrollspy({ 38 | target: '.bs-sidebar', 39 | offset: navHeight 40 | }) 41 | 42 | $window.on('load', function () { 43 | $body.scrollspy('refresh') 44 | }) 45 | 46 | $('.bs-docs-container [href=#]').click(function (e) { 47 | e.preventDefault() 48 | }) 49 | 50 | // back to top 51 | setTimeout(function () { 52 | var $sideBar = $('.bs-sidebar') 53 | 54 | $sideBar.affix({ 55 | offset: { 56 | top: function () { 57 | var offsetTop = $sideBar.offset().top 58 | var sideBarMargin = parseInt($sideBar.children(0).css('margin-top'), 10) 59 | var navOuterHeight = $('.bs-docs-nav').height() 60 | 61 | return (this.top = offsetTop - navOuterHeight - sideBarMargin) 62 | } 63 | , bottom: function () { 64 | return (this.bottom = $('.bs-footer').outerHeight(true)) 65 | } 66 | } 67 | }) 68 | }, 100) 69 | 70 | setTimeout(function () { 71 | $('.bs-top').affix() 72 | }, 100) 73 | 74 | //var offset = 60; 75 | //$('body').data().scrollspy.options.offset = offset; 76 | //$('body').data().scrollspy.process(); 77 | /*$('.bs-sidebar li a').click(function(e) { 78 | //e.preventDefault(); 79 | //$($(this).attr('href'))[0].scrollIntoView(); 80 | setTimeout(function() { 81 | scrollBy(0, -offset); 82 | }, 0); 83 | });*/ 84 | 85 | // tooltip demo 86 | $('.tooltip-demo').tooltip({ 87 | selector: "[data-toggle=tooltip]", 88 | container: "body" 89 | }) 90 | 91 | $('.tooltip-test').tooltip() 92 | $('.popover-test').popover() 93 | 94 | $('.bs-docs-navbar').tooltip({ 95 | selector: "a[data-toggle=tooltip]", 96 | container: ".bs-docs-navbar .nav" 97 | }) 98 | 99 | // popover demo 100 | $("[data-toggle=popover]") 101 | .popover() 102 | 103 | // button state demo 104 | $('#fat-btn') 105 | .click(function () { 106 | var btn = $(this) 107 | btn.button('loading') 108 | setTimeout(function () { 109 | btn.button('reset') 110 | }, 3000) 111 | }) 112 | }) 113 | 114 | }(jQuery) 115 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var expressApp = require('./src/server'); 2 | var port = process.env.PORT || 4000; 3 | var server = expressApp.listen(port, function(err) { 4 | console.log('%d listening. Go to: http://localhost:%d/', process.pid, port); 5 | }); 6 | -------------------------------------------------------------------------------- /src/server/derbyLanguage.js: -------------------------------------------------------------------------------- 1 | /* 2 | Language: DerbyJS templates 3 | Requires: xml.js 4 | Author: Nate Smith 5 | Description: Matcher for DerbyJS HTML Templates 6 | */ 7 | 8 | module.exports = function(hljs) { 9 | var TEMPLATE_KEYWORDS = 'if unless else each with on view unbound bound unescaped as'; 10 | var TEMPLATE = { 11 | className: 'template', 12 | begin: '{{', end: '}}', 13 | keywords: TEMPLATE_KEYWORDS 14 | }; 15 | var XML_IDENT_RE = '[A-Za-z0-9\\._:-]+'; 16 | var TAG_INTERNALS = { 17 | endsWithParent: true, 18 | illegal: /`]+/} 37 | ], 38 | contains: [ 39 | TEMPLATE 40 | ] 41 | } 42 | ] 43 | } 44 | ] 45 | }; 46 | return { 47 | aliases: [], 48 | case_insensitive: true, 49 | contains: [ 50 | { 51 | className: 'meta', 52 | begin: '', 53 | relevance: 10, 54 | contains: [{begin: '\\[', end: '\\]'}] 55 | }, 56 | hljs.COMMENT( 57 | '', 59 | { 60 | relevance: 10 61 | } 62 | ), 63 | { 64 | begin: '<\\!\\[CDATA\\[', end: '\\]\\]>', 65 | relevance: 10 66 | }, 67 | TEMPLATE, 68 | { 69 | className: 'tag', 70 | /* 71 | The lookahead pattern (?=...) ensures that 'begin' only matches 72 | '|$)', end: '>', 77 | keywords: {name: 'style'}, 78 | contains: [TAG_INTERNALS], 79 | starts: { 80 | end: '', returnEnd: true, 81 | subLanguage: ['css', 'xml'] 82 | } 83 | }, 84 | { 85 | className: 'tag', 86 | // See the comment in the