├── .gitattributes
├── .gitignore
├── .travis.yml
├── DO_Powered_by_Badge_blue.png
├── Gruntfile.js
├── LICENSE
├── README.md
├── bower.json
├── build
├── blocks-api-parser.js
├── init.js
├── publish
│ ├── GetClosedIssuesAndMergedPRs.graphql
│ ├── GetMasterCommits.graphql
│ └── github-graphql.js
└── tasks
│ ├── bower.js
│ ├── build-test-definitions.js
│ ├── build.js
│ ├── combine.js
│ ├── debug.js
│ ├── npm.js
│ ├── publish.js
│ └── test.js
├── dist
├── blocks-source.js
├── blocks.js
├── blocks.min.js
├── blocks.min.js.map
├── mvc
│ ├── blocks-mvc.js
│ ├── blocks-mvc.min.js
│ └── blocks-mvc.min.js.map
├── node
│ └── blocks-node.js
└── query
│ ├── blocks-query-data.js
│ ├── blocks-query-data.min.js
│ ├── blocks-query-data.min.js.map
│ ├── blocks-query.js
│ ├── blocks-query.min.js
│ └── blocks-query.min.js.map
├── lib
└── blocks
│ ├── core.js
│ ├── jsdebug.js
│ └── value-nocore.js
├── package.json
├── src
├── .jshintrc
├── DataSource.js
├── core.js
├── dataSource
│ └── DataSource.js
├── modules
│ ├── Escape.js
│ ├── Event.js
│ ├── Events.js
│ ├── Request.js
│ ├── Router.js
│ ├── ajax.js
│ ├── createProperty.js
│ ├── keys.js
│ ├── parseCallback.js
│ └── uniqueId.js
├── mvc.js
├── mvc
│ ├── Application.js
│ ├── Collection.js
│ ├── History.js
│ ├── Model.js
│ ├── Property.js
│ ├── View.js
│ ├── bindContext.js
│ ├── clonePrototype.js
│ ├── queries.js
│ ├── validation.js
│ ├── validators.js
│ └── var
│ │ ├── COLLECTION.js
│ │ └── MODEL.js
├── node.js
├── node
│ ├── .jshintrc
│ ├── BrowserEnv.js
│ ├── Middleware.js
│ ├── Server.js
│ ├── ServerEnv.js
│ ├── browserVars.js
│ ├── createBrowserEnvObject.js
│ ├── executePageScripts.js
│ ├── findPageScripts.js
│ ├── getElementsById.js
│ ├── methods.js
│ ├── overrides.js
│ └── parseToVirtual.js
├── query.js
├── query
│ ├── ChunkManager.js
│ ├── DomQuery.js
│ ├── ElementsData.js
│ ├── Expression.js
│ ├── ExtenderHelper.js
│ ├── Observer.js
│ ├── VirtualComment.js
│ ├── VirtualElement.js
│ ├── addListener.js
│ ├── animation.js
│ ├── browser.js
│ ├── createFragment.js
│ ├── createVirtual.js
│ ├── dom.js
│ ├── extenders.js
│ ├── getClassIndex.js
│ ├── methods.js
│ ├── observable.js
│ ├── on.js
│ ├── parseQuery.js
│ ├── queries.js
│ ├── ready.js
│ ├── serverData.js
│ ├── setClass.js
│ └── var
│ │ ├── OBSERVABLE.js
│ │ ├── classAttr.js
│ │ ├── dataIdAttr.js
│ │ ├── dataQueryAttr.js
│ │ ├── parameterQueryCache.js
│ │ ├── queries.js
│ │ └── virtualElementIdentity.js
└── var
│ ├── hasOwn.js
│ ├── identity.js
│ ├── slice.js
│ ├── strundefined.js
│ ├── support.js
│ ├── toString.js
│ └── trimRegExp.js
└── test
├── .jshintrc
├── Runner.html
├── blocks.testing.js
├── pages
├── input.html
├── parsing-each.html
├── parsing.html
├── styles.css
└── two-way-data-binding.html
├── runner-styles.css
├── spec
├── dataSource
│ ├── api.js
│ ├── configuration.js
│ └── events.js
├── mvc
│ ├── application.js
│ ├── collection.js
│ ├── history.js
│ ├── model.js
│ ├── property.js
│ ├── router.js
│ ├── validation.js
│ └── view.js
└── query
│ ├── attribute-queries.js
│ ├── contexts.js
│ ├── custom-queries.js
│ ├── element.js
│ ├── expressions.js
│ ├── extenders.js
│ ├── html-parsing.js
│ ├── observable.array.js
│ ├── observables.comments.js
│ ├── observables.dom.js
│ ├── observables.js
│ ├── public-methods.js
│ ├── queries.comments.js
│ └── queries.js
└── tests.json
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # JS files must always use LF for tools to work
5 | *.js eol=lf
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Folders
2 | .idea
3 | node_modules
4 | /examples
5 | /dist/npm
6 | # at least for now exclude the coverage parts
7 | /coverage
8 |
9 | # Files
10 | .DS_Store
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "6.9.4"
4 | before_install: npm install -g grunt-cli
5 | before_script:
6 | - grunt compile
7 | - export DISPLAY=:99.0
8 | - sh -e /etc/init.d/xvfb start
9 | after_success:
10 | - grunt publish
11 | addons:
12 | apt:
13 | sources:
14 | - ubuntu-toolchain-r-test
15 | packages:
16 | - g++-4.8
17 |
--------------------------------------------------------------------------------
/DO_Powered_by_Badge_blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/astoilkov/jsblocks/d3edd4b8d05960ce8785491d7c6c2bb54aae0b9b/DO_Powered_by_Badge_blue.png
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 | 'use strict';
3 |
4 | // init grunt configuration from external file
5 | require('./build/init')(grunt);
6 | };
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright jsblocks(c) 2015
4 |
5 | The following license applies to all parts of this software except as
6 | documented below:
7 |
8 | ====
9 |
10 | Permission is hereby granted, free of charge, to any person obtaining a copy
11 | of this software and associated documentation files (the "Software"), to deal
12 | in the Software without restriction, including without limitation the rights
13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 | copies of the Software, and to permit persons to whom the Software is
15 | furnished to do so, subject to the following conditions:
16 |
17 | The above copyright notice and this permission notice shall be included in all
18 | copies or substantial portions of the Software.
19 |
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 | SOFTWARE.
27 |
28 | ====
29 |
30 | All files located in the node_modules, lib and external directories are
31 | externally maintained libraries used by this software which have their
32 | own licenses; we recommend you read them, as their terms may differ from
33 | the terms above.
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ❗❗❗ I started working on this project in 2012. React didn't exist, Angular didn't have a stable 1.0 release, Internet Explorer 7, 8, and 9 was used by 35% of users worldwide, I was 20 years old. I am proud of what I did then but it was overly ambitious for a single person. The repo is now a showcase of my early skills. More about my current work [here](https://astoilkov.com).
2 |
3 | ---
4 |
5 | [](http://jsblocks.com)
6 |
7 | ### Better MV-ish Framework
8 |
9 | ##### From simple user interfaces to complex single-page applications using faster, server-side rendered and easy to learn framework.
10 |
11 | [[ official website ]](https://web.archive.org/web/20241002181521/http://jsblocks.com/)
12 |
13 | ### Features
14 |
15 | * [Server-side rendering](https://web.archive.org/web/20231207171618/http://jsblocks.com/learn/introduction-why-jsblocks#server-side-rendering)
16 | * [Debugging experience](https://web.archive.org/web/20231207171618/http://jsblocks.com/learn/introduction-why-jsblocks#debugging-experience)
17 | * [Faster](https://web.archive.org/web/20241002181521/http://jsblocks.com/#performance)
18 | * [MV-ish](https://web.archive.org/web/20231207171618/http://jsblocks.com/learn/introduction-why-jsblocks#mv-ish)
19 | * [Modular](http://jsblocks.com/learn/introduction-why-jsblocks#modular)
20 | * [Built-in utility library](https://web.archive.org/web/20231207171618/http://jsblocks.com/learn/introduction-why-jsblocks#built-in-utility-library)
21 | * [Forward thinking](https://web.archive.org/web/20231207171618/http://jsblocks.com/learn/introduction-why-jsblocks#forward-thinking)
22 | * [... and many more](https://web.archive.org/web/20231207171618/http://jsblocks.com/learn/introduction-why-jsblocks#feature-rich)
23 |
24 | ### Example projects
25 | * [TodoMVC](https://github.com/astoilkov/jsblocks-todomvc)
26 | * [E-shopping](https://github.com/astoilkov/jsblocks-shopping-example)
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "blocks",
3 | "main": "dist/blocks.js",
4 | "license": "MIT",
5 | "author": {
6 | "name": "Antonio Stoilkov",
7 | "email": "antonio.stoilkov@gmail.com",
8 | "url": "http://jsblocks.com"
9 | },
10 | "ignore": [
11 | "build",
12 | "examples",
13 | "lib",
14 | "node_modules",
15 | "src",
16 | "test",
17 | "*.gitignore",
18 | "Gruntile.js",
19 | "package.json",
20 | "dist/npm"
21 | ],
22 | "keywords": []
23 | }
--------------------------------------------------------------------------------
/build/init.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 | 'use strict';
3 |
4 | grunt.loadNpmTasks('grunt-contrib-requirejs');
5 | grunt.loadNpmTasks('grunt-contrib-uglify');
6 | grunt.loadNpmTasks('grunt-contrib-jshint');
7 | grunt.loadNpmTasks('grunt-contrib-watch');
8 | grunt.loadNpmTasks('grunt-preprocess');
9 | grunt.loadNpmTasks('grunt-karma');
10 | grunt.loadNpmTasks('grunt-notify');
11 |
12 | var pkg = grunt.file.readJSON('package.json')
13 | grunt.initConfig({
14 | pkg: pkg,
15 |
16 | version: pkg.version,
17 |
18 | banner: '/*! jsblocks v<%= pkg.version %> | ' +
19 | '(c) 2014, <%= grunt.template.today("yyyy") %> |' +
20 | 'jsblocks.org/license */',
21 |
22 | watch: {
23 | compile: {
24 | files: ['src/**/*.js'],
25 | tasks: ['compile'],
26 | options: {
27 | interrupt: true
28 | }
29 | }
30 | },
31 |
32 | preprocess: {
33 | debug: {
34 | src: ['dist/blocks-source.js'],
35 | dest: 'dist/blocks.js',
36 | options: {
37 | context: {
38 | DEBUG: true,
39 | SERVER: false
40 | }
41 | }
42 | },
43 |
44 | client: {
45 | src: [
46 | 'dist/blocks-source.js',
47 | 'dist/mvc/blocks-mvc.js',
48 | 'dist/query/blocks-query.js',
49 | 'dist/query/blocks-query-data.js'
50 | ],
51 | options: {
52 | inline: true,
53 | context: {
54 | DEBUG: false,
55 | SERVER: false
56 | }
57 | }
58 | },
59 |
60 | server: {
61 | src: 'dist/node/blocks-node.js',
62 | options: {
63 | inline: true,
64 | context: {
65 | DEBUG: false,
66 | SERVER: true
67 | }
68 | }
69 | }
70 | },
71 |
72 | uglify: {
73 | build: {
74 | options: {
75 | sourceMap: true
76 | },
77 | files: {
78 | 'dist/blocks.min.js': ['dist/blocks-source.js'],
79 | 'dist/mvc/blocks-mvc.min.js': ['dist/mvc/blocks-mvc.js'],
80 | 'dist/query/blocks-query.min.js': ['dist/query/blocks-query.js'],
81 | 'dist/query/blocks-query-data.min.js': ['dist/query/blocks-query-data.js']
82 | }
83 | }
84 | },
85 |
86 | notify: {
87 | build: {
88 | options: {
89 | message: 'Build successful'
90 | }
91 | }
92 | },
93 |
94 | jshint: {
95 | options: {
96 | jshintrc: true
97 | },
98 |
99 | source: ['src/**/*.js']
100 | //test: ['test/spec/**/*.js'],
101 | //grunt: ['build/**/*.js']
102 | }
103 | });
104 |
105 | grunt.loadTasks('build/tasks');
106 |
107 | grunt.registerTask('compile', ['build', 'combine', 'preprocess', 'debug', 'build-tests-definitions']);
108 | grunt.registerTask('live-compile', ['compile', 'watch:compile']);
109 | grunt.registerTask('full-build', ['jshint', 'compile', 'uglify', 'test', 'npm', 'bower']);
110 | grunt.registerTask('build-only', ['jshint', 'compile', 'uglify', 'npm', 'bower']);
111 | grunt.registerTask('default', []);
112 | };
113 |
--------------------------------------------------------------------------------
/build/publish/GetClosedIssuesAndMergedPRs.graphql:
--------------------------------------------------------------------------------
1 | query ($owner: String!, $repo: String!, $afterIssues: String, $afterPRs: String, $includePRs: Boolean!, $includeIssues: Boolean!) {
2 | repository(owner: $owner, name: $repo) {
3 | ...issues @include(if: $includeIssues)
4 | ...prs @include(if: $includePRs)
5 | parent {
6 | ...issues @include(if: $includeIssues)
7 | ...prs @include(if: $includePRs)
8 | }
9 | }
10 | }
11 |
12 | fragment issues on Repository {
13 | issues(first: 100, after: $afterIssues, states: [CLOSED]) {
14 | pageInfo {
15 | hasNextPage
16 | endCursor
17 | }
18 | nodes {
19 | number
20 | url
21 | title
22 | timeline(first: 100) {
23 | nodes {
24 | ... on ReferencedEvent {
25 | commit {
26 | oid
27 | }
28 | }
29 | ... on ClosedEvent {
30 | closeCommit: commit {
31 | oid
32 | }
33 | }
34 | }
35 | }
36 | }
37 | }
38 | }
39 |
40 | fragment prs on Repository {
41 | pullRequests(first: 100, after: $afterPRs, states: [MERGED]) {
42 | pageInfo {
43 | hasNextPage
44 | endCursor
45 | }
46 | nodes {
47 | title
48 | url
49 | number
50 | timeline(first: 100) {
51 | nodes {
52 | ... on MergedEvent {
53 | commit {
54 | oid
55 | }
56 | }
57 | }
58 | }
59 | # unfortunatly seems to be buggy in the alpha of github graphql
60 | # sometime "MERGED" PRs have a mergeCommit and sometimes not
61 | # while they are having a "MergedEvent" timeline
62 | # mergeCommit {
63 | # oid
64 | # }
65 | }
66 | }
67 | }
--------------------------------------------------------------------------------
/build/publish/GetMasterCommits.graphql:
--------------------------------------------------------------------------------
1 | query ($owner: String!, $repo: String!, $after: String, $includeLastRelease: Boolean!) {
2 | repository(owner: $owner, name: $repo) {
3 | ...commits
4 | ...lastRelease @include(if:$includeLastRelease)
5 | # for some reason releases aren't queryable on forked repos if they weren't published on the fork first
6 | # so include the parent in case the repo doesn't have a last release
7 | parent @include(if: $includeLastRelease) {
8 | ...lastRelease
9 | }
10 | }
11 | }
12 |
13 | fragment commits on Repository {
14 | ref(qualifiedName: "refs/heads/master") {
15 | target {
16 | ... on Commit {
17 | history(first: 100, after: $after) {
18 | pageInfo {
19 | hasNextPage
20 | endCursor
21 | }
22 | nodes {
23 | url
24 | messageHeadline
25 | oid
26 | }
27 | }
28 | }
29 | }
30 | }
31 | }
32 |
33 | fragment lastRelease on Repository {
34 | releases(last: 1) {
35 | nodes {
36 | description
37 | tag {
38 | name
39 | target {
40 | oid
41 | }
42 | }
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/build/publish/github-graphql.js:
--------------------------------------------------------------------------------
1 | var fetch = require('node-fetch');
2 | var fs = require('fs');
3 | var path = require('path');
4 |
5 | var files = {};
6 |
7 | function readFileForMethod(method) {
8 | return new Promise(function (resolve, reject) {
9 | if (files[method]) {
10 | return setTimeout(resolve.bind(null, files[method]));
11 | }
12 | fs.readFile(path.resolve(__dirname, method + '.graphql'), function (err, file) {
13 | if (err) {
14 | return reject(err);
15 | }
16 | files[method] = file.toString().replace('\n', '');
17 | resolve(files[method]);
18 | });
19 | });
20 | }
21 |
22 | function queryGraphQL (method, vars, key) {
23 | return readFileForMethod(method).then(function (query) {
24 | var body = JSON.stringify({query: query, variables: vars});
25 | return fetch('https://api.github.com/graphql', {
26 | method: 'POST',
27 | headers: {
28 | Authorization: 'bearer ' + key
29 | },
30 | body: body
31 | }).then(res => res.json()).then(function (result) {
32 | if (result.errors && result.errors.length > 0) {
33 | result.errors.forEach(console.error);
34 | throw new Error(result.error[0]);
35 | }
36 | return result;
37 | });
38 | });
39 | }
40 |
41 | function hasProperties(obj) {
42 | for (var key in obj) {
43 | return true;
44 | }
45 | return false;
46 | }
47 |
48 | function GithubGraphQLWrapper (key, owner, repo) {
49 | this._key = key;
50 | this._repo = repo;
51 | this._owner = owner;
52 | this._commits = [];
53 | this._issues = [];
54 | this._mergedPRs = [];
55 | this._closedIssues = [];
56 | this._lastRelease = null;
57 | this._commitAfterRef = null;
58 | this._reachedLastIssue = false;
59 | this._reachedLastPr = false;
60 | this._afterPRRef = null;
61 | this._afterIssueRef = null;
62 | }
63 |
64 | GithubGraphQLWrapper.prototype = {
65 | constructor: GithubGraphQLWrapper,
66 | fetchLastGithubRelease: function fetchLastGithubRelease () {
67 | return queryGraphQL('GetMasterCommits', {
68 | repo: this._repo,
69 | owner: this._owner,
70 | includeLastRelease: true,
71 | after: this._commitAfterRef
72 | }, this._key).then(result => {
73 | var data = result.data.repository;
74 | var parentRelease = data.parent && data.parent.releases.nodes.length && data.parent.releases.nodes[0];
75 | var lastRelease = data.releases.nodes.length > 0 ? data.releases.nodes[0] : parentRelease;
76 | var history = data.ref.target.history;
77 | this._lastRelease = lastRelease;
78 | this._commits = this._commits.concat(history.nodes);
79 | this._commitAfterRef = history.pageInfo.endCursor;
80 | return this;
81 | });
82 | },
83 | fetchCommitsToLastRelease: function () {
84 | return queryGraphQL('GetMasterCommits', {
85 | repo: this._repo,
86 | owner: this._owner,
87 | includeLastRelease: false,
88 | after: this._commitAfterRef
89 | }, this._key).then(result => {
90 | var data = result.data.repository;
91 | var history = data.ref.target.history;
92 | this._commitAfterRef = data.ref.target.history.pageInfo.endCursor;
93 | this._commits = this._commits.concat(data.ref.target.history.nodes);
94 | var commitOids = this._commits.map(c => c.oid);
95 | if (commitOids.indexOf(this._lastRelease.tag.target.oid) == -1 && history.pageInfo.hasNextPage) {
96 | return this.fetchCommitsToLastRelease();
97 | }
98 | this._commits.splice(commitOids.indexOf(this._lastRelease.tag.target.oid));
99 | return this;
100 | });
101 | },
102 | fetchPRsAndIssues: function () {
103 | return queryGraphQL('GetClosedIssuesAndMergedPRs', {
104 | repo: this._repo,
105 | owner: this._owner,
106 | includePRs: !this._reachedLastPr,
107 | includeIssues: !this._reachedLastIssue,
108 | afterIssues: this._afterIssueRef,
109 | afterPRs: this._afterPRRef,
110 | }, this._key).then(result => {
111 | var repository = result.data.repository;
112 | var parent = repository.parent;
113 | var parentIssues = parent && parent.issues && parent.issues.nodes.length && parent.issues;
114 | var localIssues = repository.issues && repository.issues.nodes.length && repository.issues;
115 | var issues = localIssues || parentIssues;
116 | var parentPRs = parent && parent.pullRequests && parent.pullRequests.nodes.length && parent.pullRequests;
117 | var localPRs = repository.pullRequests && repository.pullRequests.nodes.length && repository.pullRequests;
118 | var prs = localPRs || parentPRs;
119 | if (issues) {
120 | this._reachedLastIssue = !issues.pageInfo.hasNextPage;
121 | this._afterIssueRef = issues.pageInfo.endCursor;
122 | this._closedIssues = this._closedIssues.concat(issues.nodes);
123 | }
124 |
125 | if (prs) {
126 | this._reachedLastPr = !prs.pageInfo.hasNextPage;
127 | this._afterPRRef = prs.pageInfo.endCursor;
128 | this._mergedPRs = this._mergedPRs.concat(prs.nodes);
129 | }
130 | if (!this._reachedLastPr && !this._reachedLastIssue) {
131 | return this.fetchPRsAndIssues();
132 | }
133 | }).then(() => {
134 | this._closedIssues = this._closedIssues.map(issue => {
135 | issue.timeline = issue.timeline.nodes.filter(hasProperties);
136 | return issue;
137 | }).filter(issue => issue.timeline.length > 0);
138 | this._mergedPRs.map(pr => {
139 | pr.timeline = pr.timeline.nodes.filter(hasProperties);
140 | return pr;
141 | }).filter(pr => pr.timeline.length > 0);
142 | return this;
143 | });
144 | },
145 | getLastRelease: function () {
146 | return this._lastRelease;
147 | },
148 | getMergedPRs: function () {
149 | return this._mergedPRs;
150 | },
151 | getCommits: function () {
152 | return this._commits;
153 | },
154 | getClosedIssues: function () {
155 | return this._closedIssues;
156 | },
157 | getOwner: function () {
158 | return this._owner;
159 | },
160 | getRepo: function () {
161 | return this._repo;
162 | }
163 | };
164 |
165 | module.exports = GithubGraphQLWrapper;
--------------------------------------------------------------------------------
/build/tasks/bower.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 | var bowerJSON = {
3 | name: 'blocks',
4 | main: 'dist/blocks.js',
5 | license: 'MIT',
6 | author: {
7 | name: 'Antonio Stoilkov',
8 | email: 'antonio.stoilkov@gmail.com',
9 | url: 'http://jsblocks.com'
10 | },
11 | ignore: [
12 | 'build',
13 | 'examples',
14 | 'lib',
15 | 'node_modules',
16 | 'src',
17 | 'test',
18 | '*.gitignore',
19 | 'Gruntile.js',
20 | 'package.json',
21 | 'dist/npm'
22 | ],
23 | keywords: []
24 | };
25 |
26 | grunt.registerTask('bower', function () {
27 | grunt.file.write('bower.json', JSON.stringify(bowerJSON, null, 4));
28 | });
29 | };
30 |
--------------------------------------------------------------------------------
/build/tasks/build-test-definitions.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 | grunt.registerTask('build-tests-definitions', function () {
3 | var tests = {};
4 | var contents;
5 | var key;
6 |
7 | grunt.file.recurse('test/spec', function (abspath) {
8 | placeInObject(tests, abspath.replace('test/', ''), abspath.replace('test/spec/', ''));
9 | });
10 |
11 | for (key in tests) {
12 | tests['blocks.' + key] = tests[key];
13 | delete tests[key];
14 | }
15 |
16 | contents = JSON.stringify(tests, null, 4);
17 |
18 | grunt.file.write('test/tests.json', contents);
19 | });
20 |
21 | function placeInObject(current, fullPath, currentPath) {
22 | var parts = currentPath.split('/');
23 | var firstPart = parts[0];
24 | var obj;
25 |
26 | if (parts.length == 2) {
27 | (current[firstPart] = current[firstPart] || []).push(fullPath);
28 | } else {
29 | obj = current[firstPart] = current[firstPart] || {};
30 | if (obj instanceof Array) {
31 | obj.push({});
32 | obj = obj[obj.length - 1];
33 | }
34 | placeInObject(obj, fullPath, parts.slice(1).join('/'));
35 | }
36 | }
37 | };
--------------------------------------------------------------------------------
/build/tasks/build.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 | var esprima = require('esprima');
3 | var escodegen = require('escodegen');
4 | var estrvarse = require('estraverse');
5 | var definedModuleNames = {};
6 | var requirejsConfig = {};
7 | var requirejsOptions = {
8 | baseUrl: 'src',
9 | out: 'dist/<%= name %>/blocks-<%= name %>.js',
10 | include: ['<%= name %>.js'],
11 | optimize: 'none',
12 | skipSemiColonInsertion: true,
13 | onBuildWrite: function (name, path, contents) {
14 | var rdefineEnd = /\}\);[^}\w]*$/;
15 |
16 | if (/.\/var\//.test(path)) {
17 | contents = contents
18 | .replace(/define\([\w\W]*?return/, ' var ' + (/var\/([\w-]+)/.exec(name)[1]) + ' =')
19 | .replace(rdefineEnd, '');
20 |
21 | } else {
22 | contents = contents
23 | .replace(/\/\*\s*ExcludeStart\s*\*\/[\w\W]*?\/\*\s*ExcludeEnd\s*\*\//ig, '')
24 | .replace(/\/\/\s*BuildExclude\n\r?[\w\W]*?\n\r?/ig, '');
25 | var ast = esprima.parse(contents, {
26 | tokens: true,
27 | comment: true,
28 | range: true
29 | });
30 |
31 | estrvarse.attachComments(ast, ast.comments, ast.tokens);
32 |
33 | if (ast.body[0].expression.callee.name == 'define') {
34 | var moduleExpression = findModuleExpressionArgument(ast.body[0].expression.arguments);
35 | if (!moduleExpression || !moduleExpression.body.body[0] || moduleExpression.body.body[0].type == 'ReturnStatement') {
36 | // Null out empty define statements e.g. define(['./query/ready', '...'])
37 | // and expresions without an expression or only an return statement e.g. define([], function () { return blocks; })
38 | contents = '';
39 | } else {
40 | var moduleName;
41 | try {
42 | moduleName = findModuleExportIdentifier(moduleExpression.body.body) || /\/(\w+).?j?s?$/.exec(name)[1];
43 | } catch(e) {}
44 | if (moduleName && definedModuleNames[moduleName] && definedModuleNames[moduleName] != path) {
45 | grunt.fail.warn('[NamingConflict]: Module ' + path + ' tried to define ' + moduleName + ' which is already defined by ' + definedModuleNames[moduleName] + ' !');
46 | } else if (moduleName){
47 | definedModuleNames[moduleName] = path;
48 | }
49 | ast = wrapModuleAst(moduleExpression, moduleName);
50 | contents = escodegen.generate(ast, {
51 | format: {
52 | indent: {
53 | style: ' ',
54 | base: 0,
55 | adjustMultilineComment: true
56 | }
57 | },
58 | comment: true
59 | });
60 | }
61 | }
62 | /* contents = contents
63 | .replace(/\s*return\s+[^\}]+(\}\);[^\w\}]*)$/, '$1')
64 | // Multiple exports
65 | .replace(/\s*exports\.\w+\s*=\s*\w+;/g, '');
66 |
67 | // Remove define wrappers, closure ends, and empty declarations
68 | contents = contents
69 | .replace(/define\([^{]*?{/, '')
70 | .replace(rdefineEnd, '');
71 |
72 | // Remove anything wrapped with
73 | // /* ExcludeStart */ /* ExcludeEnd */
74 | // or a single line directly after a // BuildExclude comment
75 |
76 | // Remove empty definitions
77 | /* contents = contents
78 | .replace(/define\(\[[^\]]+\]\)[\W\n]+$/, '');*/
79 |
80 | }
81 |
82 | return contents;
83 | }
84 | };
85 |
86 | function findModuleExportIdentifier (module) {
87 | for (var i = module.length -1 ; i >= 0; i--) {
88 | var expression = module[i];
89 | if (expression.type == 'ReturnStatement') {
90 | return expression.argument.name;
91 | }
92 | }
93 | throw new Error('No return statement');
94 | }
95 |
96 | function findModuleExpressionArgument(args) {
97 | for (var i in args) {
98 | var arg = args[i];
99 | if (arg.type == 'FunctionExpression') {
100 | return arg;
101 | }
102 | }
103 | }
104 |
105 |
106 | function wrapModuleAst (node, exportName) {
107 | var wrapedModule;
108 | var bodyNode;
109 | if (exportName) {
110 | wrapedModule = esprima.parse('var ' + exportName + ' = (function () { })();');
111 | bodyNode = wrapedModule.body[0].declarations[0].init.callee.body;
112 | } else {
113 | wrapedModule = esprima.parse('(function () { })();');
114 | bodyNode = wrapedModule.body[0].expression.callee.body;
115 | }
116 | // insert body of the original "define"-function to the
117 | bodyNode.body = node.body.body;
118 | return wrapedModule;
119 | }
120 |
121 | var names = ['query', 'mvc', 'node'];
122 | names.forEach(function (name) {
123 | grunt.config.set('name', name);
124 | (requirejsConfig[name] = {}).options = grunt.config.process(requirejsOptions);
125 | grunt.config.set('name', undefined);
126 | });
127 |
128 | grunt.config.set('requirejs', requirejsConfig);
129 |
130 | grunt.registerTask('build', function () {
131 | var tasks = [];
132 | for (var i = 0; i < arguments.length; i++) {
133 | tasks.push('requirejs:' + arguments[i]);
134 | }
135 | grunt.task.run(tasks.length ? tasks : 'requirejs');
136 | });
137 | };
--------------------------------------------------------------------------------
/build/tasks/combine.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 |
3 | grunt.registerTask('combine', function () {
4 | var core = grunt.file.read('lib/blocks/core.js').replace('@version', grunt.config.get('version'));
5 | var jsvalue = grunt.file.read('lib/blocks/value-nocore.js');
6 | var mvc = grunt.file.read('dist/mvc/blocks-mvc.js');
7 | var query = grunt.file.read('dist/query/blocks-query.js');
8 |
9 | // TODO: Remove if node is not created until released
10 | var node = grunt.file.read('dist/node/blocks-node.js');
11 |
12 | var nodeCode = insertSourceCode(
13 | core.replace('typeof window !== \'undefined\' ? window : this', 'typeof window !== \'undefined\' && !window.__mock__ ? window : this'),
14 | [jsvalue, node]);
15 | grunt.file.write('dist/node/blocks-node.js', nodeCode);
16 |
17 |
18 | var jsblocks = insertSourceCode(core, [jsvalue, mvc]);
19 | var mvcOnly = insertSourceCode(core, [mvc]);
20 | var queryOnly = insertSourceCode(core, [query]);
21 | var queryAndValue = insertSourceCode(core, [jsvalue, query]);
22 |
23 | grunt.file.write('dist/blocks-source.js', jsblocks);
24 | grunt.file.write('dist/mvc/blocks-mvc.js', mvcOnly);
25 | grunt.file.write('dist/query/blocks-query.js', queryOnly);
26 | grunt.file.write('dist/query/blocks-query-data.js', queryAndValue);
27 | });
28 |
29 | function getSourceCodeWrap(code) {
30 | return '(function () {\n' + code + '\n})();'
31 | }
32 |
33 | function insertSourceCode(core, code) {
34 | var sourceCodeLocation;
35 | var result = core;
36 |
37 | code.forEach(function (codeBlock) {
38 | sourceCodeLocation = result.indexOf('// @source-code');
39 | result = result.substring(0, sourceCodeLocation) + '\n' + getSourceCodeWrap(codeBlock) + result.substring(sourceCodeLocation);
40 | });
41 |
42 | return result;
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/build/tasks/npm.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 | var packageJSON = {
3 | name: 'blocks',
4 | version: grunt.config.get('version'),
5 | description: 'jsblocks - Better MV-ish Framework',
6 | main: 'node/blocks-node.js',
7 | keyword: ['MVC', 'MVVM', 'MVW', 'server rendering', 'filtering', 'sorting', 'paging', 'framework'],
8 | scripts: {
9 | 'test': 'echo \'Error: no test specified\' && exit 1'
10 | },
11 | repository: {
12 | type: 'git',
13 | url: 'https://github.com/astoilkov/jsblocks.git'
14 | },
15 | author: {
16 | name: 'Antonio Stoilkov',
17 | email: 'antonio.stoilkov@gmail.com',
18 | url: 'http://jsblocks.com'
19 | },
20 | license: 'MIT',
21 | bugs: {
22 | url: 'https://github.com/astoilkov/jsblocks/issues',
23 | email: 'support@jsblocks.com'
24 | },
25 | homepage: 'https://github.com/astoilkov/jsblocks',
26 | dependencies: {
27 | express: "4.14.0",
28 | parse5: "2.2.1"
29 | }
30 | };
31 |
32 | grunt.registerTask('npm', function () {
33 | grunt.file.recurse('dist', function (abspath, rootdir, subdir, filename) {
34 | var subFolder = subdir ? subdir + '/' : '';
35 | subdir = subdir || '';
36 |
37 | if (subdir.indexOf('npm') == -1) {
38 | grunt.file.write('dist/npm/' + subFolder + filename, grunt.file.read(abspath));
39 | }
40 | });
41 | grunt.file.write('dist/npm/package.json', JSON.stringify(packageJSON, null, 4));
42 | grunt.file.write('dist/npm/README.md', grunt.file.read('README.md'));
43 | });
44 | };
--------------------------------------------------------------------------------
/build/tasks/test.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 | var karmaConfig = {
3 | options: {
4 | files: [
5 | // code for testing
6 | 'dist/blocks.js',
7 |
8 | // dependencies
9 | require.resolve('jquery/dist/jquery.js'),
10 | //'lib/jquery-1.11.2/jquery-1.11.2.js',
11 | require.resolve('jasmine-jquery'),
12 | //'lib/jasmine-jquery-2.2.0/jasmine-jquery.js',
13 | 'test/blocks.testing.js',
14 |
15 | // tests location
16 | 'test/spec/**/*.js'
17 | ],
18 | autoWatch: false,
19 | //browserNoActivityTimeout: 30000,
20 | // browserDisconnectTimeout
21 | // browserDisconnectTolerance
22 | frameworks: ['jasmine']
23 | },
24 |
25 | test: {
26 | browsers: ['Chrome', 'Firefox', 'IE11', 'IE10'],
27 | customLaunchers: {
28 | IE11: {
29 | base: 'IE',
30 | 'x-ua-compatible': 'IE=EmulateIE11'
31 | },
32 | IE10: {
33 | base: 'IE',
34 | 'x-ua-compatible': 'IE=EmulateIE10'
35 | }
36 | },
37 | singleRun: true
38 | },
39 |
40 | coverage: {
41 | browserConsoleLogOptions: {
42 | terminal: false
43 | },
44 | reporters: ['dots', 'coverage'],
45 | preprocessors: {
46 | 'dist/blocks.js': ['coverage']
47 | },
48 | coverageReporter: {
49 | type: 'html',
50 | dir: 'coverage/'
51 | },
52 | browsers: ['Firefox'],
53 | singleRun: true
54 | },
55 |
56 | watch: {
57 | browsers: ['PhantomJS'],
58 | background: true
59 | },
60 | phantom: {
61 | browsers: ['PhantomJS'],
62 | singleRun: true
63 | },
64 |
65 | chrome: {
66 | browsers: ['Chrome'],
67 | singleRun: true
68 | },
69 |
70 | firefox: {
71 | browsers: ['Firefox'],
72 | singleRun: true
73 | },
74 |
75 | ie: {
76 | browsers: ['IE11', 'IE10', 'IE9'],
77 | customLaunchers: {
78 | IE11: {
79 | base: 'IE',
80 | 'x-ua-compatible': 'IE=EmulateIE11'
81 | },
82 | IE10: {
83 | base: 'IE',
84 | 'x-ua-compatible': 'IE=EmulateIE10'
85 | },
86 | IE9: {
87 | base: 'IE',
88 | 'x-ua-compatible': 'IE=EmulateIE9'
89 | },
90 | IE8: {
91 | base: 'IE',
92 | 'x-ua-compatible': 'IE=EmulateIE8'
93 | }
94 | },
95 | singleRun: true
96 | },
97 |
98 | ie9: {
99 | browsers: ['IE9'],
100 | customLaunchers: {
101 | IE9: {
102 | base: 'IE',
103 | 'x-ua-compatible': 'IE=EmulateIE9'
104 | }
105 | },
106 | singleRun: true
107 | },
108 |
109 | safari: {
110 | browsers: ['Safari'],
111 | singleRun: true
112 | },
113 |
114 | opera: {
115 | browsers: ['Opera'],
116 | singleRun: true
117 | },
118 |
119 |
120 | build: {
121 | browsers: ['IE', 'ChromeCanary', 'Safari', 'Firefox', 'Chrome', 'PhantomJS'],
122 | singleRun: true
123 | }
124 | };
125 |
126 | grunt.config.set('karma', karmaConfig);
127 |
128 | grunt.registerTask('test', function (browser) {
129 | if (browser) {
130 | grunt.task.run('karma:' + browser);
131 | } else {
132 | grunt.task.run('karma:test');
133 | }
134 | });
135 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jsblocks",
3 | "version": "0.3.5",
4 | "description": "Better MV-ish Framework",
5 | "author": {
6 | "name": "Antonio Stoilkov",
7 | "email": "antonio.stoilkov@gmail.com",
8 | "url": "http://jsblocks.com"
9 | },
10 | "license": "MIT",
11 | "scripts": {
12 | "test": "grunt test:firefox"
13 | },
14 | "devDependencies": {
15 | "blocks": "^0.3.4",
16 | "escodegen": "^1.6.1",
17 | "esprima": "^3.0.0",
18 | "estraverse": "^4.1.0",
19 | "grunt": "^1.0.1",
20 | "grunt-cli": "^1.2.0",
21 | "grunt-contrib-jshint": "^1.0.0",
22 | "grunt-contrib-requirejs": "^1.0.0",
23 | "grunt-contrib-uglify": "^2.0.0",
24 | "grunt-contrib-watch": "^1.0.0",
25 | "grunt-karma": "^2.0.0",
26 | "grunt-notify": "^0.4.1",
27 | "grunt-preprocess": "^5.1.0",
28 | "highlight.js": "^9.6.0",
29 | "jasmine-core": "^2.2.0",
30 | "jasmine-jquery": "^2.1.1",
31 | "jquery": "^3.2.1",
32 | "karma": "^1.2.0",
33 | "karma-chrome-launcher": "^2.0.0",
34 | "karma-coverage": "^1.1.1",
35 | "karma-firefox-launcher": "^1.0.0",
36 | "karma-ie-launcher": "^1.0.0",
37 | "karma-jasmine": "^1.0.2",
38 | "karma-jasmine-jquery": "^0.1.1",
39 | "node-fetch": "^1.6.3",
40 | "nodegit": "^0.18.0",
41 | "npm-utils": "^1.11.0",
42 | "parse5": "^2.2.1"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "eqeqeq": false,
3 | "forin": false,
4 | "plusplus": false,
5 | "strict": false,
6 | "newcap": false,
7 |
8 | "es3": true,
9 | "bitwise": true,
10 | "camelcase": true,
11 | "curly": true,
12 | "freeze": true,
13 | "immed": true,
14 | "latedef": false,
15 | "noarg": true,
16 | "noempty": true,
17 | "nonbsp": true,
18 | "nonew": true,
19 | "undef": true,
20 | "unused": true,
21 | "trailing": true,
22 | "loopfunc": true,
23 | "eqnull": true,
24 | "quotmark": "single",
25 |
26 | "browser": true,
27 |
28 | "globals": {
29 | "blocks": true,
30 | "define": true,
31 | "global": true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/DataSource.js:
--------------------------------------------------------------------------------
1 | define([
2 | './dataSource/DataSource'
3 | ], function (DataSource) {
4 | return DataSource;
5 | });
--------------------------------------------------------------------------------
/src/core.js:
--------------------------------------------------------------------------------
1 | define([
2 |
3 | ], function (blocks) {
4 | return blocks;
5 | });
6 |
--------------------------------------------------------------------------------
/src/modules/Escape.js:
--------------------------------------------------------------------------------
1 | define([],
2 | function () {
3 | var htmlEntityMap = {
4 | '&': '&',
5 | '<': '<',
6 | '>': '>',
7 | '"': '"',
8 | '\'': ''',
9 | '/': '/'
10 | };
11 |
12 | var htmlEscapeRegEx = (function () {
13 | var entities = [];
14 | for (var entity in htmlEntityMap) {
15 | entities.push(entity);
16 | }
17 | return new RegExp('(' + entities.join('|') + ')', 'g');
18 | })();
19 |
20 | function internalHTMLEscapeReplacer(entity) {
21 | return htmlEntityMap[entity];
22 | }
23 |
24 | var Escape = {
25 | // moved from modules/escapeRegEx
26 | forRegEx: function escapeRegEx(string) {
27 | return string.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
28 | },
29 | forHTML: function (value) {
30 | if (blocks.isString(value)) {
31 | return value.replace(htmlEscapeRegEx, internalHTMLEscapeReplacer);
32 | }
33 | return value;
34 | },
35 | // This is only valid because jsblocks forces (inserts itself) double quotes for attributes
36 | // don't use this in other cases
37 | forHTMLAttributes: function (value) {
38 | if (blocks.isString(value)) {
39 | return value.replace(/"/g, '"');
40 | }
41 | return value;
42 | }
43 | };
44 | return Escape;
45 | });
46 |
--------------------------------------------------------------------------------
/src/modules/Event.js:
--------------------------------------------------------------------------------
1 | define(function () {
2 | var isMouseEventRegEx = /^(?:mouse|pointer|contextmenu)|click/;
3 | var isKeyEventRegEx = /^key/;
4 |
5 | function returnFalse() {
6 | return false;
7 | }
8 |
9 | function returnTrue() {
10 | return true;
11 | }
12 |
13 | function Event(e) {
14 | this.originalEvent = e;
15 | this.type = e.type;
16 |
17 | this.isDefaultPrevented = e.defaultPrevented ||
18 | (e.defaultPrevented === undefined &&
19 | // Support: IE < 9, Android < 4.0
20 | e.returnValue === false) ?
21 | returnTrue :
22 | returnFalse;
23 |
24 | this.timeStamp = e.timeStamp || +new Date();
25 | }
26 |
27 | Event.PropertiesToCopy = {
28 | all: 'altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which'.split(' '),
29 | mouse: 'button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement'.split(' '),
30 | keyboard: 'char charCode key keyCode'.split(' ')
31 | };
32 |
33 | Event.CopyProperties = function (originalEvent, event, propertiesName) {
34 | blocks.each(Event.PropertiesToCopy[propertiesName], function (propertyName) {
35 | event[propertyName] = originalEvent[propertyName];
36 | });
37 | };
38 |
39 | Event.prototype = {
40 | preventDefault: function () {
41 | var e = this.originalEvent;
42 |
43 | this.isDefaultPrevented = returnTrue;
44 |
45 | if (e.preventDefault) {
46 | // If preventDefault exists, run it on the original event
47 | e.preventDefault();
48 | } else {
49 | // Support: IE
50 | // Otherwise set the returnValue property of the original event to false
51 | e.returnValue = false;
52 | }
53 | },
54 |
55 | stopPropagation: function () {
56 | var e = this.originalEvent;
57 |
58 | this.isPropagationStopped = returnTrue;
59 |
60 | // If stopPropagation exists, run it on the original event
61 | if (e.stopPropagation) {
62 | e.stopPropagation();
63 | }
64 |
65 | // Support: IE
66 | // Set the cancelBubble property of the original event to true
67 | e.cancelBubble = true;
68 | },
69 |
70 | stopImmediatePropagation: function () {
71 | var e = this.originalEvent;
72 |
73 | this.isImmediatePropagationStopped = returnTrue;
74 |
75 | if (e.stopImmediatePropagation) {
76 | e.stopImmediatePropagation();
77 | }
78 |
79 | this.stopPropagation();
80 | }
81 | };
82 |
83 | Event.fix = function (originalEvent) {
84 | var type = originalEvent.type;
85 | var event = new Event(originalEvent);
86 |
87 | Event.CopyProperties(originalEvent, event, 'all');
88 |
89 | // Support: IE<9
90 | // Fix target property (#1925)
91 | if (!event.target) {
92 | event.target = originalEvent.srcElement || document;
93 | }
94 |
95 | // Support: Chrome 23+, Safari?
96 | // Target should not be a text node (#504, #13143)
97 | if (event.target.nodeType === 3) {
98 | event.target = event.target.parentNode;
99 | }
100 |
101 | // Support: IE<9
102 | // For mouse/key events, metaKey==false if it's undefined (#3368, #11328)
103 | event.metaKey = !!event.metaKey;
104 |
105 | if (isMouseEventRegEx.test(type)) {
106 | Event.fixMouse(originalEvent, event);
107 | } else if (isKeyEventRegEx.test(type) && event.which == null) {
108 | Event.CopyProperties(originalEvent, event, 'keyboard');
109 | // Add which for key events
110 | event.which = originalEvent.charCode != null ? originalEvent.charCode : originalEvent.keyCode;
111 | }
112 |
113 | return event;
114 | };
115 |
116 | Event.fixMouse = function (originalEvent, event) {
117 | var button = originalEvent.button;
118 | var fromElement = originalEvent.fromElement;
119 | var body;
120 | var eventDoc;
121 | var doc;
122 |
123 | Event.CopyProperties(originalEvent, event, 'mouse');
124 |
125 | // Calculate pageX/Y if missing and clientX/Y available
126 | if (event.pageX == null && originalEvent.clientX != null) {
127 | eventDoc = event.target.ownerDocument || document;
128 | doc = eventDoc.documentElement;
129 | body = eventDoc.body;
130 |
131 | event.pageX = originalEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
132 | event.pageY = originalEvent.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
133 | }
134 |
135 | // Add relatedTarget, if necessary
136 | if (!event.relatedTarget && fromElement) {
137 | event.relatedTarget = fromElement === event.target ? originalEvent.toElement : fromElement;
138 | }
139 |
140 | // Add which for click: 1 === left; 2 === middle; 3 === right
141 | // Note: button is not normalized, so don't use it
142 | if (!event.which && button !== undefined) {
143 | /* jshint bitwise: false */
144 | event.which = (button & 1 ? 1 : (button & 2 ? 3 : (button & 4 ? 2 : 0)));
145 | }
146 | };
147 |
148 | //var event = blocks.Event();
149 | //event.currentTarget = 1; // the current element from which is the event is fired
150 | //event.namespace = ''; // the namespace for the event
151 |
152 | return Event;
153 | });
154 |
--------------------------------------------------------------------------------
/src/modules/Events.js:
--------------------------------------------------------------------------------
1 | define([
2 | '../core'
3 | ], function (blocks) {
4 | var Events = (function () {
5 | function createEventMethod(eventName) {
6 | return function (callback, context) {
7 | if (arguments.length > 1) {
8 | Events.on(this, eventName, callback, context);
9 | } else {
10 | Events.on(this, eventName, callback);
11 | }
12 | return this;
13 | };
14 | }
15 |
16 | var methods = {
17 | on: function (eventName, callback, context) {
18 | if (arguments.length > 2) {
19 | Events.on(this, eventName, callback, context);
20 | } else {
21 | Events.on(this, eventName, callback);
22 | }
23 | return this;
24 | },
25 |
26 | once: function (eventNames, callback, thisArg) {
27 | Events.once(this, eventNames, callback, thisArg);
28 | },
29 |
30 | off: function (eventName, callback) {
31 | Events.off(this, eventName, callback);
32 | },
33 |
34 | trigger: function (eventName) {
35 | Events.trigger(this, eventName, blocks.toArray(arguments).slice(1, 100));
36 | }
37 | };
38 | methods._trigger = methods.trigger;
39 |
40 | return {
41 | register: function (object, eventNames) {
42 | eventNames = blocks.isArray(eventNames) ? eventNames : [eventNames];
43 | for (var i = 0; i < eventNames.length; i++) {
44 | var methodName = eventNames[i];
45 | if (methods[methodName]) {
46 | object[methodName] = methods[methodName];
47 | } else {
48 | object[methodName] = createEventMethod(methodName);
49 | }
50 | }
51 | },
52 |
53 | on: function (object, eventNames, callback, thisArg) {
54 | eventNames = blocks.toArray(eventNames).join(' ').split(' ');
55 |
56 | var i = 0;
57 | var length = eventNames.length;
58 | var eventName;
59 |
60 | if (!callback) {
61 | return;
62 | }
63 |
64 | if (!object._events) {
65 | object._events = {};
66 | }
67 | for (; i < length; i++) {
68 | eventName = eventNames[i];
69 | if (!object._events[eventName]) {
70 | object._events[eventName] = [];
71 | }
72 | object._events[eventName].push({
73 | callback: callback,
74 | thisArg: thisArg
75 | });
76 | }
77 | },
78 |
79 | once: function (object, eventNames, callback, thisArg) {
80 | Events.on(object, eventNames, callback, thisArg);
81 | Events.on(object, eventNames, function () {
82 | Events.off(object, eventNames, callback);
83 | });
84 | },
85 |
86 | off: function (object, eventName, callback) {
87 | if (blocks.isFunction(eventName)) {
88 | callback = eventName;
89 | eventName = undefined;
90 | }
91 |
92 | if (eventName !== undefined || callback !== undefined) {
93 | blocks.each(object._events, function (events, currentEventName) {
94 | if (eventName !== undefined && callback === undefined) {
95 | object._events[eventName] = [];
96 | } else {
97 | blocks.each(events, function (eventData, index) {
98 | if (eventData.callback == callback) {
99 | object._events[currentEventName].splice(index, 1);
100 | return false;
101 | }
102 | });
103 | }
104 | });
105 | } else {
106 | object._events = undefined;
107 | }
108 | },
109 |
110 | trigger: function (object, eventName) {
111 | var result = true;
112 | var eventsData;
113 | var thisArg;
114 | var args;
115 |
116 | if (object && object._events) {
117 | eventsData = object._events[eventName];
118 |
119 | if (eventsData && eventsData.length > 0) {
120 | args = Array.prototype.slice.call(arguments, 2);
121 |
122 | blocks.each(eventsData, function iterateEventsData(eventData) {
123 | if (eventData) {
124 | thisArg = object;
125 | if (eventData.thisArg !== undefined) {
126 | thisArg = eventData.thisArg;
127 | }
128 | if (eventData.callback.apply(thisArg, args) === false) {
129 | result = false;
130 | }
131 | }
132 | });
133 | }
134 | }
135 |
136 | return result;
137 | },
138 |
139 | has: function (object, eventName) {
140 | return !!blocks.access(object, '_events.' + eventName + '.length');
141 | }
142 | };
143 | })();
144 |
145 | return Events;
146 | });
--------------------------------------------------------------------------------
/src/modules/Request.js:
--------------------------------------------------------------------------------
1 | define([
2 | '../core',
3 | '../modules/uniqueId',
4 | '../query/serverData'
5 | ], function (blocks, uniqueId, serverData) {
6 | function Request(options) {
7 | this.options = blocks.extend({}, Request.Defaults, options);
8 | this.execute();
9 | }
10 |
11 | Request.Execute = function (options) {
12 | return new Request(options);
13 | };
14 |
15 | Request.Defaults = {
16 | type: 'GET',
17 | url: '',
18 | processData: true,
19 | async: true,
20 | contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
21 | jsonp: 'callback',
22 | jsonpCallback: function () {
23 | return uniqueId();
24 | }
25 |
26 | /*
27 | timeout: 0,
28 | data: null,
29 | dataType: null,
30 | username: null,
31 | password: null,
32 | cache: null,
33 | throws: false,
34 | traditional: false,
35 | headers: {},
36 | */
37 | };
38 |
39 | Request.Accepts = {
40 | '*': '*/'.concat('*'),
41 | text: 'text/plain',
42 | html: 'text/html',
43 | xml: 'application/xml, text/xml',
44 | json: 'application/json, text/javascript'
45 | };
46 |
47 | Request.Meta = {
48 | statusFix: {
49 | // file protocol always yields status code 0, assume 200
50 | 0: 200,
51 | // Support: IE9
52 | // IE sometimes returns 1223 instead of 204
53 | 1223: 204
54 | }
55 | };
56 |
57 | Request.prototype = {
58 | execute: function () {
59 | var options = this.options;
60 |
61 | if (options.type == 'GET' && options.data) {
62 | this.appendDataToUrl(options.data);
63 | }
64 |
65 | if (serverData.hasData && serverData.data.requests && serverData.data.requests[options.url]) {
66 | setTimeout(function (self) {
67 | self.callSuccess(serverData.data.requests[options.url]);
68 | }, 5, this);
69 | } else {
70 | try {
71 | if (options.dataType == 'jsonp') {
72 | this.scriptRequest();
73 | } else {
74 | this.xhrRequest();
75 | }
76 | } catch (e) {
77 |
78 | }
79 | }
80 | },
81 |
82 | xhrRequest: function () {
83 | var options = this.options;
84 | var xhr = this.createXHR();
85 |
86 | xhr.onabort = blocks.bind(this.xhrError, this);
87 | xhr.ontimeout = blocks.bind(this.xhrError, this);
88 | xhr.onload = blocks.bind(this.xhrLoad, this);
89 | xhr.onerror = blocks.bind(this.xhrError, this);
90 | xhr.open(options.type.toUpperCase(), options.url, options.async, options.username, options.password);
91 | xhr.setRequestHeader('Content-Type', options.contentType);
92 | xhr.setRequestHeader('Accept', Request.Accepts[options.dataType || '*']);
93 | xhr.send(options.data || null);
94 | },
95 |
96 | createXHR: function () {
97 | var Type = XMLHttpRequest || window.ActiveXObject;
98 | try {
99 | return new Type('Microsoft.XMLHTTP');
100 | } catch (e) {
101 |
102 | }
103 | },
104 |
105 | xhrLoad: function (e) {
106 | var request = e.target;
107 | var status = Request.Meta.statusFix[request.status] || request.status;
108 | var isSuccess = status >= 200 && status < 300 || status === 304;
109 | if (isSuccess) {
110 | this.callSuccess(request.responseText);
111 | } else {
112 | this.callError(request.statusText);
113 | }
114 | },
115 |
116 | xhrError: function () {
117 | this.callError();
118 | },
119 |
120 | scriptRequest: function () {
121 | var that = this;
122 | var options = this.options;
123 | var script = document.createElement('script');
124 | var jsonpCallback = {};
125 | var callbackName = blocks.isFunction(options.jsonpCallback) ? options.jsonpCallback() : options.jsonpCallback;
126 |
127 | jsonpCallback[options.jsonp] = callbackName;
128 | this.appendDataToUrl(jsonpCallback);
129 | window[callbackName] = function (result) {
130 | window[callbackName] = null;
131 | that.scriptLoad(result);
132 | };
133 |
134 | script.onerror = this.scriptError;
135 | script.async = options.async;
136 | script.src = options.url;
137 | document.head.appendChild(script);
138 | },
139 |
140 | scriptLoad: function (data) {
141 | this.callSuccess(data);
142 | },
143 |
144 | scriptError: function () {
145 | this.callError();
146 | },
147 |
148 | appendDataToUrl: function (data) {
149 | var that = this;
150 | var options = this.options;
151 | var hasParameter = /\?/.test(options.url);
152 |
153 | if (blocks.isPlainObject(data)) {
154 | blocks.each(data, function (value, key) {
155 | options.url += that.append(hasParameter, key, value.toString());
156 | });
157 | } else if (blocks.isArray(data)) {
158 | blocks.each(data, function (index, value) {
159 | that.appendDataToUrl(value);
160 | });
161 | } else {
162 | options.url += that.append(hasParameter, data.toString(), '');
163 | }
164 | },
165 |
166 | append: function (hasParameter, key, value) {
167 | var result = hasParameter ? '&' : '?';
168 | result += key;
169 | if (value) {
170 | result += '=' + value;
171 | }
172 | return result;
173 | },
174 |
175 | callSuccess: function (data) {
176 | var success = this.options.success;
177 | var textStatus = 'success';
178 | if (success) {
179 | success(data, textStatus, null);
180 | }
181 | this.callComplete(textStatus);
182 | },
183 |
184 | callError: function (errorThrown) {
185 | var error = this.options.error;
186 | var textStatus = 'error';
187 | if (error) {
188 | error(null, textStatus, errorThrown);
189 | }
190 | this.callComplete(textStatus);
191 | },
192 |
193 | callComplete: function (textStatus) {
194 | var complete = this.options.complete;
195 | if (complete) {
196 | complete(null, textStatus);
197 | }
198 | }
199 | };
200 | return Request;
201 | });
202 |
--------------------------------------------------------------------------------
/src/modules/ajax.js:
--------------------------------------------------------------------------------
1 | define([
2 | './Request'
3 | ], function (Request) {
4 | function ajax(options) {
5 | if (window) {
6 | var jQuery = window.jQuery || window.$;
7 | if (jQuery && jQuery.ajax) {
8 | jQuery.ajax(options);
9 | } else {
10 | Request.Execute(options);
11 | }
12 | }
13 | }
14 |
15 | return ajax;
16 | });
--------------------------------------------------------------------------------
/src/modules/createProperty.js:
--------------------------------------------------------------------------------
1 | define(function () {
2 | function createProperty(propertyName) {
3 | return function (value) {
4 | if (arguments.length === 0) {
5 | return this[propertyName];
6 | }
7 | this[propertyName] = value;
8 | return this;
9 | };
10 | }
11 |
12 | return createProperty;
13 | });
--------------------------------------------------------------------------------
/src/modules/keys.js:
--------------------------------------------------------------------------------
1 | define(function () {
2 | function keys(array) {
3 | var result = {};
4 | blocks.each(array, function (value) {
5 | result[value] = true;
6 | });
7 | return result;
8 | }
9 |
10 | return keys;
11 | });
--------------------------------------------------------------------------------
/src/modules/parseCallback.js:
--------------------------------------------------------------------------------
1 | define(function () {
2 | function parseCallback(callback, thisArg) {
3 | //callback = parseExpression(callback);
4 | if (thisArg != null) {
5 | var orgCallback = callback;
6 | callback = function (value, index, collection) {
7 | return orgCallback.call(thisArg, value, index, collection);
8 | };
9 | }
10 | return callback;
11 | }
12 |
13 | return parseCallback;
14 | });
--------------------------------------------------------------------------------
/src/modules/uniqueId.js:
--------------------------------------------------------------------------------
1 | define([
2 | '../core'
3 | ], function (blocks) {
4 | var uniqueId = (function () {
5 | var timeStamp = Date.now();
6 | return function () {
7 | return 'blocks_' + blocks.version + '_' + timeStamp++;
8 | };
9 | })();
10 |
11 | return uniqueId;
12 | });
13 |
--------------------------------------------------------------------------------
/src/mvc.js:
--------------------------------------------------------------------------------
1 | define([
2 | './core',
3 | './query',
4 | './mvc/queries',
5 | './mvc/validators',
6 | './mvc/validation',
7 | './mvc/Application'
8 | ], function () {
9 |
10 | });
11 |
--------------------------------------------------------------------------------
/src/mvc/Property.js:
--------------------------------------------------------------------------------
1 | define([
2 | '../core'
3 | ], function (blocks) {
4 | function Property(options) {
5 | this._options = options || {};
6 | }
7 |
8 | Property.Is = function (value) {
9 | return Property.prototype.isPrototypeOf(value);
10 | };
11 |
12 | Property.Inflate = function (object) {
13 | var properties = {};
14 | var key;
15 | var value;
16 | var defaultValue;
17 |
18 | for (key in object) {
19 | value = object[key];
20 | if (Property.Is(value)) {
21 | value = value._options;
22 | defaultValue = value.defaultValue;
23 | value.propertyName = key;
24 | properties[value.field || key] = value;
25 | }
26 | }
27 |
28 | return properties;
29 | };
30 |
31 | Property.Create = function (options, thisArg, value) {
32 | var observable;
33 |
34 | if (arguments.length < 3) {
35 | value = options.value || options.defaultValue;
36 | }
37 | thisArg = options.thisArg ? options.thisArg : thisArg;
38 |
39 | value = blocks.clone(value);
40 |
41 | observable = blocks
42 | .observable(value, thisArg)
43 | .extend('validation', options)
44 | .on('changing', options.changing, thisArg)
45 | .on('change', options.change, thisArg);
46 |
47 | if (options.extenders) {
48 | blocks.each(options.extenders, function (extendee) {
49 | observable = observable.extend.apply(observable, extendee);
50 | });
51 | }
52 |
53 | return observable;
54 | };
55 |
56 | Property.prototype.extend = function () {
57 | var options = this._options;
58 | options.extenders = options.extenders || [];
59 | options.extenders.push(blocks.toArray(arguments));
60 |
61 | return this;
62 | };
63 | return Property;
64 | });
65 |
--------------------------------------------------------------------------------
/src/mvc/View.js:
--------------------------------------------------------------------------------
1 | define([
2 | '../core',
3 | '../modules/ajax',
4 | '../modules/Events',
5 | './bindContext',
6 | '../query/serverData'
7 | ], function (blocks, ajax, Events, bindContext, serverData) {
8 | /**
9 | * @namespace View
10 | */
11 | function View(application, parentView) {
12 | var _this = this;
13 |
14 | bindContext(this);
15 | this._views = [];
16 | this._application = application;
17 | this._parentView = parentView || null;
18 | this._initCalled = false;
19 | this._html = undefined;
20 |
21 | this.loading = blocks.observable(false);
22 | this.isActive = blocks.observable(!blocks.has(this.options, 'route'));
23 | this.isActive.on('changing', function (oldValue, newValue) {
24 | _this._tryInitialize(newValue);
25 | });
26 |
27 | if (this.options.preload || this.isActive()) {
28 | this._load();
29 | }
30 | }
31 |
32 | View.prototype = {
33 | /**
34 | * Determines if the view is visible or not.
35 | * This property is automatically populated when routing is enabled for the view.
36 | *
37 | * @memberof View
38 | * @name isActive
39 | * @type {blocks.observable}
40 | */
41 |
42 | /**
43 | * Override the init method to perform actions when the View is first created
44 | * and shown on the page
45 | *
46 | * @memberof View
47 | * @type {Function}
48 | *
49 | * @example {javascript}
50 | * var App = blocks.Application();
51 | *
52 | * App.View('Statistics', {
53 | * init: function () {
54 | * this.loadRemoteData();
55 | * },
56 | *
57 | * loadRemoteData: function () {
58 | * // ...stuff...
59 | * }
60 | * });
61 | */
62 | init: blocks.noop,
63 |
64 | /**
65 | * Override the ready method to perform actions when the DOM is ready and
66 | * all data-query have been executed.
67 | *
68 | * @memberof View
69 | * @type {Function}
70 | *
71 | * @example {javascript}
72 | * var App = blocks.Application();
73 | *
74 | * App.View('ContactUs', {
75 | * ready: function () {
76 | * $('#contact-form').ajaxSubmit();
77 | * }
78 | * });
79 | */
80 | ready: blocks.noop,
81 |
82 | /**
83 | * Override the routed method to perform actions when the View have routing and routing
84 | * mechanism actives it.
85 | *
86 | * @memberof View
87 | * @type {Function}
88 | *
89 | * @example {javascript}
90 | * var App = blocks.Application();
91 | *
92 | * App.View('ContactUs', {
93 | * options: {
94 | * route: 'contactus'
95 | * },
96 | *
97 | * routed: function () {
98 | * alert('Navigated to ContactUs page!')
99 | * }
100 | * });
101 | */
102 | routed: blocks.noop,
103 |
104 | /**
105 | * Observable which value is true when the View html
106 | * is being loaded using ajax request. It could be used
107 | * to show a loading indicator.
108 | *
109 | * @memberof View
110 | */
111 | loading: blocks.observable(false),
112 |
113 | /**
114 | * Gets the parent view.
115 | * Returns null if the view is not a child of another view.
116 | *
117 | * @memberof View
118 | */
119 | parentView: function () {
120 | return this._parentView;
121 | },
122 |
123 | /**
124 | * Routes to a specific URL and actives the appropriate views associated with the URL
125 | *
126 | * @memberof View
127 | * @param {String} name -
128 | * @returns {View} - Chainable. Returns this
129 | *
130 | * @example {javascript}
131 | * var App = blocks.Application();
132 | *
133 | * App.View('ContactUs', {
134 | * options: {
135 | * route: 'contactus'
136 | * }
137 | * });
138 | *
139 | * App.View('Navigation', {
140 | * navigateToContactUs: function () {
141 | * this.route('contactus')
142 | * }
143 | * });
144 | */
145 | route: function (/* name */ /*, ...params */) {
146 | this._application._history.navigate(blocks.toArray(arguments).join('/'));
147 | return this;
148 | },
149 |
150 | navigateTo: function (view, params) {
151 | this._application.navigateTo(view, params);
152 | },
153 |
154 | _tryInitialize: function (isActive) {
155 | if (!this._initialized && isActive) {
156 | if (this.options.url && !this._html) {
157 | this._callInit();
158 | this._load();
159 | } else {
160 | this._initialized = true;
161 | this._callInit();
162 | if (this.isActive()) {
163 | this.isActive.update();
164 | }
165 | }
166 | }
167 | },
168 |
169 | _routed: function (params, metadata) {
170 | this._tryInitialize(true);
171 | this.routed(params, metadata);
172 | blocks.each(this._views, function (view) {
173 | if (!view.options.route) {
174 | view._routed(params, metadata);
175 | }
176 | });
177 | this.isActive(true);
178 | },
179 |
180 | _callInit: function () {
181 | if (this._initCalled) {
182 | return;
183 | }
184 |
185 | var key;
186 | var value;
187 |
188 | blocks.__viewInInitialize__ = this;
189 | for (key in this) {
190 | value = this[key];
191 | if (blocks.isObservable(value)) {
192 | value.__context__ = this;
193 | }
194 | }
195 | this.init();
196 | blocks.__viewInInitialize__ = undefined;
197 | this._initCalled = true;
198 | },
199 |
200 | _load: function () {
201 | var url = this.options.url;
202 | if (serverData.hasData && serverData.data.views && serverData.data.views[url]) {
203 | url = this.options.url = undefined;
204 | this._tryInitialize(true);
205 | }
206 |
207 | if (url && !this.loading()) {
208 | this.loading(true);
209 | ajax({
210 | isView: true,
211 | url: url,
212 | success: blocks.bind(this._loaded, this),
213 | error: blocks.bind(this._error, this)
214 | });
215 | }
216 | },
217 |
218 | _loaded: function (html) {
219 | this._html = html;
220 | this._tryInitialize(true);
221 | this.loading(false);
222 | },
223 |
224 | _error: function () {
225 | this.loading(false);
226 | },
227 | // View is a singleton so return a reference
228 | clone: function () {
229 | return this;
230 | }
231 | };
232 |
233 | Events.register(View.prototype, ['on', 'off', 'trigger']);
234 |
235 | /* @if DEBUG */ {
236 | blocks.debug.addType('View', function (value) {
237 | if (value && View.prototype.isPrototypeOf(value)) {
238 | return true;
239 | }
240 | return false;
241 | });
242 | } /* @endif */
243 | return View;
244 | });
245 |
--------------------------------------------------------------------------------
/src/mvc/bindContext.js:
--------------------------------------------------------------------------------
1 | define([
2 | '../core'
3 | ], function (blocks) {
4 | function bindContext (context, object) {
5 | var key;
6 | var value;
7 | /* @if DEBUG */ blocks.debug.pause(); /* @endif */
8 | if (!object) {
9 | object = context;
10 | }
11 |
12 | for (key in object) {
13 | value = object[key];
14 |
15 | if (blocks.isObservable(value)) {
16 | context[key].__context__ = context;
17 | } else if (blocks.isFunction(value)) {
18 | context[key] = blocks.bind(value, context);
19 | }
20 |
21 | }
22 | /* @if DEBUG */ blocks.debug.resume(); /* @endif */
23 | }
24 | return bindContext;
25 | });
--------------------------------------------------------------------------------
/src/mvc/clonePrototype.js:
--------------------------------------------------------------------------------
1 | define([
2 | './Model',
3 | './Property'
4 | ], function (Model, Property) {
5 | function clonePrototype(prototype, object) {
6 | var key;
7 | var value;
8 |
9 | for (key in prototype) {
10 | value = prototype[key];
11 | if (Property.Is(value)) {
12 | continue;
13 | }
14 |
15 | if (blocks.isObservable(value)) {
16 | // clone the observable and also its value by passing true to the clone method
17 | object[key] = value.clone(true);
18 | object[key].__context__ = object;
19 | } else if (blocks.isFunction(value)) {
20 | object[key] = blocks.bind(value, object);
21 | } else if (Model.prototype.isPrototypeOf(value)) {
22 | object[key] = value.clone(true);
23 | } else if (blocks.isObject(value) && !blocks.isPlainObject(value)) {
24 | object[key] = blocks.clone(value, true);
25 | } else {
26 | object[key] = blocks.clone(value, true);
27 | }
28 | }
29 | }
30 |
31 | return clonePrototype;
32 | });
33 |
--------------------------------------------------------------------------------
/src/mvc/queries.js:
--------------------------------------------------------------------------------
1 | define([
2 | '../core',
3 | '../query/var/queries',
4 | '../query/animation',
5 | '../query/addListener',
6 | '../query/createVirtual',
7 | '../query/VirtualElement',
8 | '../query/Expression'
9 | ], function (blocks, queries, animation, addListener, createVirtual, VirtualElement, Expression) {
10 | blocks.extend(blocks.queries, {
11 | /**
12 | * Associates the element with the particular view and creates a $view context property.
13 | * The View will be automatically hidden and shown if the view have routing. The visibility
14 | * of the View could be also controlled using the isActive observable property
15 | *
16 | * @memberof blocks.queries
17 | * @param {View} view - The view to associate with the current element
18 | *
19 | * @example {html}
20 | *
21 | *
22 | *
23 | *
24 | *
25 | * - {{username}}
26 | *
27 | *
28 | *
29 | * @example {javascript}
30 | * var App = blocks.Application();
31 | *
32 | * App.View('Profiles', {
33 | * users: [{ username: 'John' }, { username: 'Doe' }],
34 | *
35 | * selectUser: function (e) {
36 | * // ...stuff...
37 | * }
38 | * });
39 | */
40 | view: {
41 | passDomQuery: true,
42 |
43 | preprocess: function (domQuery, view) {
44 | if (!view.isActive()) {
45 | this.css('display', 'none');
46 | } else {
47 | //view._tryInitialize(view.isActive());
48 | this.css('display', '');
49 | if (view._html) {
50 | blocks.queries.template.preprocess.call(this, domQuery, view._html, view);
51 | }
52 | // Quotes are used because of IE8 and below. It fails with 'Expected idenfitier'
53 | //queries['with'].preprocess.call(this, domQuery, view, '$view');
54 | //queries.define.preprocess.call(this, domQuery, view._name, view);
55 | }
56 |
57 | queries['with'].preprocess.call(this, domQuery, view, '$view');
58 | },
59 |
60 | update: function (domQuery, view) {
61 | if (view.isActive()) {
62 | if (view._html) {
63 | // Quotes are used because of IE8 and below. It fails with 'Expected idenfitier'
64 | queries['with'].preprocess.call(this, domQuery, view, '$view');
65 |
66 | this.innerHTML = view._html;
67 | view._children = view._html = undefined;
68 | blocks.each(createVirtual(this.childNodes[0]), function (element) {
69 | if (VirtualElement.Is(element)) {
70 | element.sync(domQuery);
71 | } else if (element && element.isExpression && element.element) {
72 | element.element.nodeValue = Expression.GetValue(domQuery._context, null, element);
73 | }
74 | });
75 | domQuery.createElementObservableDependencies(this.childNodes);
76 | }
77 | animation.show(this);
78 | } else {
79 | animation.hide(this);
80 | }
81 | }
82 | },
83 |
84 | /**
85 | * Navigates to a particular view by specifying the target view or route and optional parameters
86 | *
87 | * @memberof blocks.queries
88 | * @param {(View|String)} viewOrRoute - the view or route to which to navigate to
89 | * @param {Object} [params] - parameters needed for the current route
90 | *
91 | * @example {html}
92 | *
93 | * Contact Us
94 | *
95 | *
96 | * T-Shirts
97 | *
98 | *
99 | * T-Shirts
100 | */
101 | navigateTo: {
102 | update: function (viewOrRoute, params) {
103 | function navigate(e) {
104 | e = e || window.event;
105 | e.preventDefault();
106 | e.returnValue = false;
107 |
108 | if (blocks.isString(viewOrRoute)) {
109 | window.location.href = viewOrRoute;
110 | } else {
111 | viewOrRoute.navigateTo(viewOrRoute, params);
112 | }
113 | }
114 |
115 | addListener(this, 'click', navigate);
116 | }
117 | },
118 |
119 | trigger: {
120 |
121 | }
122 | });
123 | });
124 |
--------------------------------------------------------------------------------
/src/mvc/validation.js:
--------------------------------------------------------------------------------
1 | define([
2 | '../core',
3 | '../modules/Events',
4 | '../query/observable',
5 | './validators'
6 | ], function (blocks, Events, observable, validators) {
7 | // TODO: asyncValidate
8 | blocks.observable.validation = function (options) {
9 | var _this = this;
10 | var maxErrors = options.maxErrors;
11 | var errorMessages = this.errorMessages = blocks.observable([]);
12 | var validatorsArray = this._validators = [];
13 | var key;
14 | var option;
15 |
16 | this.errorMessage = blocks.observable('');
17 |
18 | for (key in options) {
19 | option = options[key];
20 | if (validators[key]) {
21 | validatorsArray.push({
22 | option: option,
23 | validate: validators[key].validate,
24 | priority: validators[key].priority
25 | });
26 | } else if (key == 'validate' || key == 'asyncValidate') {
27 | validatorsArray.push({
28 | option: '',
29 | validate: option.validate || option,
30 | priority: option.priority || Number.POSITIVE_INFINITY,
31 | isAsync: key == 'asyncValidate'
32 | });
33 | }
34 | }
35 |
36 | validatorsArray.sort(function (a, b) {
37 | return a.priority > b.priority ? 1 : -1;
38 | });
39 |
40 | this.valid = blocks.observable(true);
41 |
42 | this.hasValidator = function (validator) {
43 | if (!validator) {
44 | return validatorsArray.length > 0;
45 | }
46 |
47 | if (blocks.isString(validator)) {
48 | validator = validators[validator].validate;
49 | }
50 |
51 | if (!validator) {
52 | return false;
53 | }
54 | for (var i = 0; i < validatorsArray.length; i++) {
55 | if (validatorsArray[i].validate == validator) {
56 | return true;
57 | }
58 | }
59 | return false;
60 | };
61 |
62 | this.validate = function () {
63 | var value = _this._getValue();
64 | var isValid = true;
65 | var errorsCount = 0;
66 | var i = 0;
67 | var validationOptions;
68 | var validator;
69 | var message;
70 |
71 | errorMessages.removeAll();
72 | for (; i < validatorsArray.length; i++) {
73 | if (errorsCount >= maxErrors) {
74 | break;
75 | }
76 | validator = validatorsArray[i];
77 | if (validator.isAsync) {
78 | validator.validate.call(_this.__context__, value, function (result) {
79 | validationComplete(_this, options, !!result);
80 | });
81 | return true;
82 | } else {
83 | validationOptions = validator.option;
84 | option = validator.option;
85 | if (blocks.isPlainObject(validationOptions)) {
86 | option = validationOptions.value;
87 | }
88 | if (blocks.isFunction(option)) {
89 | option = option.call(_this.__context__);
90 | }
91 | message = validator.validate.call(_this.__context__, value, options, option);
92 | if (blocks.isString(message)) {
93 | message = [message];
94 | }
95 | if (blocks.isArray(message) || !message) {
96 | errorMessages.addMany(
97 | blocks.isArray(message) ? message :
98 | validationOptions && validationOptions.message ? [validationOptions.message] :
99 | option && blocks.isString(option) ? [option] :
100 | []);
101 | isValid = false;
102 | errorsCount++;
103 | }
104 | }
105 | }
106 |
107 | validationComplete(this, options, isValid);
108 | this.valid(isValid);
109 | Events.trigger(this, 'validate');
110 | return isValid;
111 | };
112 |
113 | if (options.validateOnChange) {
114 | this.on('change', function () {
115 | this.validate();
116 | });
117 | }
118 | if (options.validateInitially) {
119 | this.validate();
120 | }
121 | };
122 |
123 | function validationComplete(observable, options, isValid) {
124 | var errorMessage = observable.errorMessage;
125 | var errorMessages = observable.errorMessages;
126 |
127 | if (isValid) {
128 | errorMessage('');
129 | } else {
130 | errorMessage(options.errorMessage || errorMessages()[0] || '');
131 | }
132 |
133 | observable.valid(isValid);
134 | }
135 | });
136 |
--------------------------------------------------------------------------------
/src/mvc/validators.js:
--------------------------------------------------------------------------------
1 | define([
2 | '../core'
3 | ], function (blocks) {
4 | var validators = {
5 | required: {
6 | priority: 9,
7 | validate: function (value, options) {
8 | if (value !== options.defaultValue &&
9 | value !== '' &&
10 | value !== false &&
11 | value !== undefined &&
12 | value !== null) {
13 | return true;
14 | }
15 | }
16 | },
17 |
18 | minlength: {
19 | priority: 19,
20 | validate: function (value, options, option) {
21 | if (value === undefined || value === null) {
22 | return false;
23 | }
24 | return value.length >= parseInt(option, 10);
25 | }
26 | },
27 |
28 | maxlength: {
29 | priority: 29,
30 | validate: function (value, options, option) {
31 | if (value === undefined || value === null) {
32 | return true;
33 | }
34 | return value.length <= parseInt(option, 10);
35 | }
36 | },
37 |
38 | min: {
39 | priority: 39,
40 | validate: function (value, options, option) {
41 | if (value === undefined || value === null) {
42 | return false;
43 | }
44 | return value >= option;
45 | }
46 | },
47 |
48 | max: {
49 | priority: 49,
50 | validate: function (value, options, option) {
51 | if (value === undefined || value === null) {
52 | return false;
53 | }
54 | return value <= option;
55 | }
56 | },
57 |
58 | email: {
59 | priority: 59,
60 | validate: function (value) {
61 | return /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i.test(value);
62 | }
63 | },
64 |
65 | url: {
66 | priority: 69,
67 | validate: function (value) {
68 | return /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/.test(value);
69 | }
70 | },
71 |
72 | date: {
73 | priority: 79,
74 | validate: function (value) {
75 | if (!value) {
76 | return false;
77 | }
78 | return !/Invalid|NaN/.test(new Date(value.toString()).toString());
79 | }
80 | },
81 |
82 | creditcard: {
83 | priority: 89,
84 | validate: function (value) {
85 | if (blocks.isString(value) && value.length === 0) {
86 | return false;
87 | }
88 | if (blocks.isNumber(value)) {
89 | value = value.toString();
90 | }
91 | // accept only spaces, digits and dashes
92 | if (/[^0-9 \-]+/.test(value)) {
93 | return false;
94 | }
95 | var nCheck = 0,
96 | nDigit = 0,
97 | bEven = false;
98 |
99 | value = value.replace(/\D/g, '');
100 |
101 | for (var n = value.length - 1; n >= 0; n--) {
102 | var cDigit = value.charAt(n);
103 | nDigit = parseInt(cDigit, 10);
104 | if (bEven) {
105 | if ((nDigit *= 2) > 9) {
106 | nDigit -= 9;
107 | }
108 | }
109 | nCheck += nDigit;
110 | bEven = !bEven;
111 | }
112 |
113 | return (nCheck % 10) === 0;
114 | }
115 | },
116 |
117 | regexp: {
118 | priority: 99,
119 | validate: function (value, options, option) {
120 | if (!blocks.isRegExp(option)) {
121 | return false;
122 | }
123 | if (value === undefined || value === null) {
124 | return false;
125 | }
126 | return option.test(value);
127 | }
128 | },
129 |
130 | number: {
131 | priority: 109,
132 | validate: function (value) {
133 | if (blocks.isNumber(value)) {
134 | return true;
135 | }
136 | if (blocks.isString(value) && value.length === 0) {
137 | return false;
138 | }
139 | return /^(-?|\+?)(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value);
140 | }
141 | },
142 |
143 | digits: {
144 | priority: 119,
145 | validate: function (value) {
146 | return /^\d+$/.test(value);
147 | }
148 | },
149 |
150 | letters: {
151 | priority: 129,
152 | validate: function (value) {
153 | if (!value) {
154 | return false;
155 | }
156 | return /^[a-zA-Z]+$/.test(value);
157 | }
158 | },
159 |
160 | equals: {
161 | priority: 139,
162 | validate: function (value, options, option) {
163 | return blocks.equals(value, blocks.unwrap(option));
164 | }
165 | }
166 | };
167 |
168 | return validators;
169 | });
170 |
--------------------------------------------------------------------------------
/src/mvc/var/COLLECTION.js:
--------------------------------------------------------------------------------
1 | define(function () {
2 | return '__blocks.Application.Collection__';
3 | });
--------------------------------------------------------------------------------
/src/mvc/var/MODEL.js:
--------------------------------------------------------------------------------
1 | define(function () {
2 | return '__blocks.Application.Model__';
3 | });
--------------------------------------------------------------------------------
/src/node.js:
--------------------------------------------------------------------------------
1 | define([
2 | './mvc',
3 | './node/methods',
4 | './node/overrides'
5 | ], function () {
6 |
7 | });
--------------------------------------------------------------------------------
/src/node/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "browser": false,
3 |
4 | "globals": {
5 | "require": true,
6 | "server": true
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/node/BrowserEnv.js:
--------------------------------------------------------------------------------
1 | define([
2 | './createBrowserEnvObject',
3 | '../core'
4 | ], function (createBrowserEnvObject, blocks) {
5 | var url = require('url');
6 |
7 | function BrowserEnv() {
8 | var env = createBrowserEnvObject();
9 | this._env = env;
10 |
11 | this._initialize();
12 | }
13 |
14 | BrowserEnv.Create = function () {
15 | return new BrowserEnv();
16 | };
17 |
18 | BrowserEnv.prototype = {
19 | getObject: function () {
20 | return this._env;
21 | },
22 |
23 | fillLocation: function (fullUrl) {
24 | var props = url.parse(fullUrl);
25 | var copy = 'host hostname href pathname protocol'.split(' ');
26 | var location = this._env.window.location;
27 |
28 | blocks.each(copy, function (name) {
29 | location[name] = props[name];
30 | });
31 | },
32 | setBaseUrl: function (url) {
33 | if (url) {
34 | this._env.__baseUrl__ = url;
35 | }
36 | },
37 | addElementsById: function (elementsById) {
38 | var env = this._env;
39 | env.document.__elementsById__ = elementsById;
40 | blocks.each(elementsById, function (element, id) {
41 | env[id] = element;
42 | });
43 | },
44 |
45 | fillServer: function (server) {
46 | var window = this._env.window;
47 | var timeout = setTimeout;
48 | var clear = clearTimeout;
49 |
50 | window.setTimeout = function (callback, delay) {
51 | if (delay === 0) {
52 | server.wait();
53 | return timeout(function () {
54 | var result = callback();
55 |
56 | server.ready();
57 |
58 | return result;
59 | }, delay);
60 | }
61 |
62 | return timeout(blocks.noop, delay);
63 | };
64 |
65 | window.clearTimeout = function (id) {
66 | return clear(id);
67 | };
68 | },
69 |
70 | _initialize: function () {
71 | var env = this._env;
72 | var document = env.document;
73 | env.window._blocks = blocks;
74 |
75 | document.getElementById = function (id) {
76 | return (document.__elementsById__ || {})[id] || null;
77 | };
78 | }
79 | };
80 | return BrowserEnv;
81 | });
--------------------------------------------------------------------------------
/src/node/Middleware.js:
--------------------------------------------------------------------------------
1 | define([
2 | '../core',
3 | './findPageScripts',
4 | './executePageScripts',
5 | './getElementsById',
6 | './parseToVirtual',
7 | './ServerEnv',
8 | './BrowserEnv',
9 | '../query/VirtualElement'
10 | ], function (blocks, findPageScripts, executePageScripts, getElementsById, parseToVirtual, ServerEnv, BrowserEnv, VirtualElement) {
11 | var fs = require('fs');
12 | var path = require('path');
13 |
14 | function Middleware(options) {
15 | if (blocks.isString(options)) {
16 | options = {
17 | static: options
18 | };
19 | }
20 |
21 | this._options = blocks.extend({}, Middleware.Defaults, options);
22 | this._contents = '';
23 | this._scripts = [];
24 | this._cache = {};
25 | this._initialized = false;
26 |
27 | this._initialize();
28 | }
29 |
30 | Middleware.Defaults = {
31 | static: 'app',
32 | cache: true,
33 | baseTag: false
34 | };
35 |
36 | Middleware.prototype = {
37 | tryServePage: function (req, res, next) {
38 | this._renderContents(req, function (err, contents) {
39 | if (err && (err != 'no query' || req.url != '/')) {
40 | next();
41 | } else {
42 | res.send(contents);
43 | return;
44 | }
45 | });
46 | },
47 |
48 | _initialize: function () {
49 | var _this = this;
50 | var url = path.join(this._options.static, '/index.html');
51 |
52 | fs.readFile(url, { encoding: 'utf-8' }, function (err, contents) {
53 | if (!err) {
54 | _this._setContents(contents);
55 | }
56 | });
57 | },
58 |
59 | _setContents: function (contents) {
60 | var _this = this;
61 | var virtual = blocks.first(parseToVirtual(contents), function (child) {
62 | return VirtualElement.Is(child);
63 | });
64 |
65 | this._contents = contents;
66 | this._elementsById = getElementsById(virtual.children());
67 |
68 | findPageScripts(virtual, this._options.static, function (scripts) {
69 | _this._scripts = scripts;
70 | _this._initialized = true;
71 | });
72 | },
73 |
74 | _renderContents: function (req, callback) {
75 | var cache = this._cache;
76 | var location = this._getLocation(req);
77 | var env;
78 |
79 | if (!this._initialized) {
80 | callback('not initialized', null);
81 | } else if (this._options.cache && cache[location]) {
82 | callback(null, cache[location]);
83 | } else {
84 | env = this._createEnv(req);
85 | executePageScripts(env, this._scripts, this._pageExecuted.bind(this, callback, env.location.href));
86 | }
87 | },
88 |
89 | _pageExecuted: function (callback, location, err, contents) {
90 | if (!err && this._options.cache) {
91 | this._cache[location] = contents;
92 | }
93 | callback(err, contents);
94 | },
95 |
96 | _createEnv: function (req) {
97 | var server = new ServerEnv(this._options, this._contents);
98 |
99 | return blocks.extend({ server: server }, this._createBrowserEnv(req, server));
100 | },
101 |
102 | _createBrowserEnv: function (req, server) {
103 | var browserEnv = BrowserEnv.Create();
104 |
105 | browserEnv.setBaseUrl(req.baseUrl);
106 | browserEnv.fillLocation(this._getLocation(req));
107 | browserEnv.addElementsById(this._elementsById);
108 | browserEnv.fillServer(server);
109 |
110 | return browserEnv.getObject();
111 | },
112 |
113 | _getLocation: function (req) {
114 | return req.protocol + '://' + req.get('host') + req.originalUrl;
115 | }
116 | };
117 |
118 | return Middleware;
119 | });
--------------------------------------------------------------------------------
/src/node/Server.js:
--------------------------------------------------------------------------------
1 | define([
2 | '../core',
3 | './Middleware'
4 | ], function (blocks, Middleware) {
5 | var path = require('path');
6 | var express = require('express');
7 |
8 | function Server(options) {
9 | this._options = blocks.extend({}, Server.Defaults, options);
10 | this._app = express();
11 | this._middleware = new Middleware(options);
12 |
13 | this._init();
14 | }
15 |
16 | Server.Defaults = blocks.extend({}, Middleware.Defaults, {
17 | port: 8000,
18 | use: null
19 | });
20 |
21 | Server.prototype = {
22 | _started: false,
23 | /**
24 | * Returns the express instance used by the blocks server internally.
25 | * When called synchronous directly after the server has been crated
26 | * pre middlewares can be added.
27 | * But the server will need to be started explicitly.
28 | * @return {object} the express app
29 | * @example {javascript}
30 | * var server = blocks.server();
31 | * var app = blocks.express();
32 | * app.use('/test', function (req, res) {
33 | * res.json({some: 'test'});
34 | * });
35 | * // required for ssr to work now
36 | * server.start();
37 | */
38 | express: function () {
39 | this._delayStart = true;
40 | return this._app;
41 | },
42 |
43 | _init: function () {
44 | var options = this._options;
45 | var app = this._app;
46 | var self = this;
47 |
48 | app.listen(options.port);
49 |
50 | blocks.each(blocks.toArray(options.use), function (middleware) {
51 | if (blocks.isFunction(middleware)) {
52 | app.use(middleware);
53 | }
54 | });
55 |
56 | app.use(express.static(path.resolve(options.static), {
57 | index: false
58 | }));
59 |
60 | process.nextTick(function () {
61 | if (!self._delayStart) {
62 | self.start();
63 | }
64 | });
65 |
66 | },
67 |
68 | /**
69 | * Starts the ssr server.
70 | * Does not need to be called if server.express()
71 | * is not called.
72 | */
73 | start: function () {
74 | if (this._started) {
75 | return;
76 | }
77 | var middleware = this._middleware;
78 | this._app.get('/*', function (req, res, next) {
79 | middleware.tryServePage(req, res, next);
80 | });
81 | this._started = true;
82 | },
83 |
84 | /**
85 | * Returns if the server has been started.
86 | * @return {boolean}
87 | */
88 | started: function () {
89 | return this._started;
90 | }
91 | };
92 | return Server;
93 | });
--------------------------------------------------------------------------------
/src/node/ServerEnv.js:
--------------------------------------------------------------------------------
1 | define([
2 | '../core',
3 | '../modules/Events'
4 | ], function (blocks, Events) {
5 | function ServerEnv(options, html) {
6 | this.options = options;
7 | this.html = html;
8 | this.data = {};
9 | this._root = this._createBubbleNode();
10 | this._node = this._root;
11 |
12 | this.on('ready', blocks.bind(this._ready, this));
13 | }
14 |
15 | ServerEnv.prototype = {
16 | _waiting: 0,
17 |
18 | rendered: '',
19 |
20 | isReady: function () {
21 | return this._waiting === 0;
22 | },
23 |
24 | wait: function () {
25 | this._waiting += 1;
26 | },
27 |
28 | ready: function () {
29 | if (this._waiting > 0) {
30 | this._waiting -= 1;
31 | if (this._waiting === 0) {
32 | this.trigger('ready');
33 | }
34 | }
35 | },
36 |
37 | await: function (callback) {
38 | if (this.isReady()) {
39 | callback();
40 | } else {
41 | this._createBubbleNode(this._node, callback);
42 | }
43 | },
44 |
45 | _ready: function () {
46 | var node = this._node;
47 |
48 | while (this.isReady()) {
49 | if (!node.isRoot) {
50 | node.callback();
51 | }
52 | this._node = node = this._next(node);
53 |
54 | if (node === this._root) {
55 | break;
56 | }
57 | }
58 | },
59 |
60 | _next: function (node) {
61 | var parent = node;
62 | var next;
63 |
64 | while (!next && parent) {
65 | next = parent.nodes.pop();
66 | parent = parent.parent;
67 | }
68 |
69 | return next || this._root;
70 | },
71 |
72 | _createBubbleNode: function (parent, callback) {
73 | var node = {
74 | isRoot: !parent,
75 | parent: parent,
76 | callback: callback,
77 | nodes: []
78 | };
79 |
80 | if (parent) {
81 | parent.nodes.unshift(node);
82 | }
83 |
84 | return node;
85 | }
86 | };
87 |
88 | Events.register(ServerEnv.prototype, ['on', 'once', 'off', 'trigger']);
89 |
90 | return ServerEnv;
91 | });
--------------------------------------------------------------------------------
/src/node/createBrowserEnvObject.js:
--------------------------------------------------------------------------------
1 | define(function () {
2 | function createBrowserEnvObject() {
3 | var windowObj = createWindowContext();
4 |
5 | return blocks.extend(windowObj, {
6 | document: createDocumentContext()
7 | });
8 | }
9 |
10 | function createDocumentContext() {
11 | var base = createElementMock();
12 |
13 | return blocks.extend(base, {
14 | __mock__: true,
15 |
16 | doctype: createElementMock(),
17 |
18 | body: createElementMock(),
19 |
20 | createElement: function (tagName) {
21 | return createElementMock(tagName);
22 | },
23 |
24 | getElementById: function () {
25 | return null;
26 | }
27 | });
28 | }
29 |
30 | function createWindowContext() {
31 | var timeoutId = 0;
32 | var windowObj = {
33 | __mock__: true,
34 | __baseUrl__: '',
35 |
36 | console: createConsoleMock(),
37 |
38 | history: {
39 | length: 1,
40 | state: null,
41 | back: blocks.noop,
42 | forward: blocks.noop,
43 | go: blocks.noop,
44 | pushState: blocks.noop,
45 | replaceState: blocks.noop
46 | },
47 |
48 | location: {
49 | ancestorOrigins: [],
50 | assign: blocks.noop,
51 | hash: '',
52 | host: '',
53 | hostname: '',
54 | href: '',
55 | origin: '',
56 | pathname: '',
57 | protocol: '',
58 | reload: blocks.noop,
59 | replace: blocks.noop,
60 | search: ''
61 | },
62 |
63 | navigator: {
64 | appCodeName: 'Mozilla',
65 | appName: 'Netscape',
66 | appVersion: '5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36',
67 | cookieEnabled: true,
68 | doNotTrack: null,
69 | //geolocation: {},
70 | hardwareConcurrency: 8,
71 | language: 'en-us',
72 | languages: ['en-US', 'en'],
73 | maxTouchPoints: 0,
74 | mimeTypes: [],
75 | onLine: true,
76 | platform: 'Win32',
77 | plugins: [],
78 | product: 'Gecko',
79 | productSub: '20030107',
80 | serviceWorker: {},
81 | userAgent: 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36',
82 | vendor: 'Google Inc.',
83 | vendorSub: ''
84 | // webkitPersistentStorage: {},
85 | // webkitTemporaryStorage: {}
86 | },
87 |
88 | addEventListener: blocks.noop,
89 | removeEventListener: blocks.noop,
90 |
91 | setTimeout: function () {
92 | timeoutId += 1;
93 | return timeoutId;
94 | },
95 | clearTimeout: blocks.noop,
96 |
97 | setInterval: function () {
98 | timeoutId += 1;
99 | return timeoutId;
100 | },
101 | clearInterval: blocks.noop
102 | };
103 |
104 | windowObj.window = windowObj;
105 |
106 | return windowObj;
107 | }
108 |
109 | function createElementMock(tagName) {
110 | return {
111 | accessKey: '',
112 | tagName: String(tagName).toLowerCase(),
113 | getElementsByClassName: function () {
114 | return [];
115 | },
116 | getElementsByTagName: function () {
117 | return [];
118 | },
119 | addEventListener: blocks.noop,
120 | removeEventListener: blocks.noop,
121 | contains: function () {
122 | return true;
123 | }
124 | };
125 | }
126 |
127 | function createConsoleMock() {
128 | return {
129 | log: blocks.noop
130 | };
131 | }
132 |
133 | return createBrowserEnvObject;
134 | });
--------------------------------------------------------------------------------
/src/node/executePageScripts.js:
--------------------------------------------------------------------------------
1 | define([
2 | '../core',
3 | '../query/ElementsData'
4 | ], function (blocks, ElementsData) {
5 | // var vm = require('vm');
6 |
7 | function executePageScripts(env, scripts, callback) {
8 | var code = 'with(window) {';
9 |
10 | blocks.each(scripts, function (script) {
11 | code += script.code + ';';
12 | });
13 |
14 | code += '}';
15 | executeCode(env, code, callback);
16 | }
17 |
18 | var funcs = {};
19 | function executeCode(env, code, callback) {
20 | contextBubble(env, function () {
21 | blocks.core.deleteApplication();
22 | ElementsData.reset();
23 |
24 | if (!funcs[code]) {
25 | // jshint -W054
26 | // Disable JSHint error: The Function constructor is a form of eval
27 | funcs[code] = new Function('blocks', 'document', 'window', 'require', code);
28 | }
29 |
30 | funcs[code].call(this, blocks, env.document, env.window, require);
31 |
32 | server.await(function () {
33 | handleResult(env, callback);
34 | });
35 | });
36 | }
37 |
38 | function contextBubble(obj, callback) {
39 | var _this = this;
40 | var values = {};
41 |
42 | blocks.each(obj, function (val, name) {
43 | values[name] = _this[name];
44 | _this[name] = val;
45 | });
46 |
47 | callback();
48 |
49 | server.await(function () {
50 | blocks.each(obj, function (val, name) {
51 | _this[name] = values[name];
52 | });
53 | }, true);
54 | }
55 |
56 | function handleResult(env, callback) {
57 | var hasRoute = false;
58 | var hasActive = false;
59 | var application = env.server.application;
60 | if (application) {
61 | application._createViews();
62 | application._startHistory();
63 |
64 | server.await(function () {
65 | blocks.query(application);
66 | blocks.each(application._views, function (view) {
67 | if (blocks.has(view.options, 'route')) {
68 | hasRoute = true;
69 | }
70 | if (view.isActive()) {
71 | hasActive = true;
72 | }
73 | });
74 | });
75 | }
76 |
77 | server.await(function () {
78 | if (hasRoute && !hasActive) {
79 | callback('not found', null);
80 | }
81 |
82 | if (env.server.rendered) {
83 | callback(null, env.server.rendered);
84 | } else {
85 | callback('no query', env.server.html);
86 | }
87 | });
88 | }
89 |
90 |
91 | // function executeCode(browserEnv, html, code) {
92 | // var context = vm.createContext(browserEnv.getObject());
93 | // var script = vm.createScript(code);
94 | //
95 | // blocks.extend(context, {
96 | // server: {
97 | // html: html,
98 | // data: {},
99 | // rendered: '',
100 | // applications: []
101 | // },
102 | // require: require
103 | // });
104 | //
105 | // script.runInContext(context);
106 | //
107 | // blocks.each(context.server.applications, function (application) {
108 | // application.start();
109 | // });
110 | //
111 | // return context.server.rendered || html;
112 | // }
113 |
114 | return executePageScripts;
115 | });
--------------------------------------------------------------------------------
/src/node/findPageScripts.js:
--------------------------------------------------------------------------------
1 | define([
2 | '../core',
3 | '../query/VirtualElement'
4 | ], function (blocks, VirtualElement) {
5 | var path = require('path');
6 | var fs = require('fs');
7 |
8 | function findPageScripts(virtual, static, callback) {
9 | var scripts = [];
10 | var args = {
11 | filesPending: 0,
12 | callback: callback,
13 | static: static
14 | };
15 | findPageScriptsRecurse(virtual, scripts, args);
16 | if (args.filesPending === 0) {
17 | args.callback([]);
18 | }
19 | }
20 |
21 | function findPageScriptsRecurse(virtual, scripts, args) {
22 | blocks.each(virtual.children(), function (child) {
23 | if (!VirtualElement.Is(child)) {
24 | return;
25 | }
26 | var src;
27 |
28 | if (child.tagName() == 'script' && (!child.attr('type') || child.attr('type') == 'text/javascript')) {
29 | src = child.attr('src');
30 | if (src && !child.attr('data-client-only')) {
31 | src = path.join(args.static, src);
32 |
33 | scripts.push({
34 | type: 'external',
35 | url: src,
36 | code: ''
37 | });
38 |
39 | args.filesPending += 1;
40 | populateScript(scripts[scripts.length - 1], function () {
41 | args.filesPending -= 1;
42 | if (args.filesPending === 0) {
43 | args.callback(scripts);
44 | }
45 | });
46 | } else {
47 | scripts.push({
48 | type: 'page',
49 | code: child.renderChildren()
50 | });
51 | }
52 | }
53 | findPageScriptsRecurse(child, scripts, args);
54 | });
55 | }
56 |
57 | function populateScript(script, callback) {
58 | fs.readFile(script.url, { encoding: 'utf-8' }, function (err, code) {
59 | script.code = code;
60 | callback();
61 | });
62 | }
63 |
64 | return findPageScripts;
65 | });
66 |
--------------------------------------------------------------------------------
/src/node/getElementsById.js:
--------------------------------------------------------------------------------
1 | define([
2 | '../core',
3 | '../query/VirtualElement'
4 | ], function (blocks, VirtualElement) {
5 | function getElementsById(elements, result) {
6 | result = result || {};
7 |
8 | blocks.each(elements, function (child) {
9 | if (VirtualElement.Is(child)) {
10 | if (child.attr('id')) {
11 | result[child.attr('id')] = child;
12 | }
13 | getElementsById(child.children(), result);
14 | }
15 | });
16 |
17 | return result;
18 | }
19 |
20 | return getElementsById;
21 | });
--------------------------------------------------------------------------------
/src/node/methods.js:
--------------------------------------------------------------------------------
1 | define([
2 | '../core',
3 | './Server',
4 | './Middleware'
5 | ], function (blocks, Server, Middleware) {
6 | blocks.server = function (options) {
7 | return new Server(options);
8 | };
9 |
10 | blocks.static = function (options) {
11 |
12 | };
13 |
14 | blocks.middleware = function (options) {
15 | var express = require('express');
16 | var path = require('path');
17 | // the real middleware
18 | var middleware;
19 | // array of middlewares to return
20 | var middlewares = [];
21 |
22 | var defaults = {
23 | // Should the files in static get delivered
24 | deliverStatics: true,
25 | baseTag: true
26 | };
27 |
28 | if (blocks.isString(options)) {
29 | options = {
30 | static: options
31 | };
32 | }
33 |
34 | options = blocks.extend({}, Middleware.Defaults, defaults, options);
35 | middleware = new Middleware(options);
36 |
37 | if (options.deliverStatics) {
38 | // express.static is required as the frontend app needs it's files
39 | middlewares.push(express.static(path.resolve(options.static), {
40 | index: false
41 | }));
42 | }
43 |
44 | middlewares.push(blocks.bind(middleware.tryServePage, middleware));
45 |
46 | // returning array of express middlewares that express will call in that order
47 | return middlewares;
48 | };
49 | });
--------------------------------------------------------------------------------
/src/node/overrides.js:
--------------------------------------------------------------------------------
1 | define([
2 | '../core',
3 | '../modules/Request',
4 | '../query/var/dataIdAttr',
5 | '../query/DomQuery',
6 | '../query/VirtualElement',
7 | '../mvc/Application',
8 | './parseToVirtual',
9 | '../modules/Escape'
10 | ], function (blocks, Request, dataIdAttr, DomQuery, VirtualElement, Application, parseToVirtual, Escape) {
11 | var eachQuery = blocks.queries.each.preprocess;
12 |
13 | blocks.queries.each.preprocess = function (domQuery, collection) {
14 | if (!server.data[this._attributes[dataIdAttr]]) {
15 | removeDataIds(this);
16 | server.data[this._attributes[dataIdAttr]] = this.renderChildren();
17 | }
18 |
19 | eachQuery.call(this, domQuery, collection);
20 | };
21 |
22 | function removeDataIds(element) {
23 | var children = element._template || element._children;
24 | blocks.each(children, function (child) {
25 | if (VirtualElement.Is(child)) {
26 | child._attributes['data-id'] = null;
27 | removeDataIds(child);
28 | }
29 | });
30 | }
31 |
32 | blocks.query = function (model) {
33 | var domQuery = new DomQuery(model);
34 | var children = parseToVirtual(server.html);
35 |
36 | domQuery.pushContext(model);
37 |
38 | renderChildren(children, domQuery);
39 | };
40 |
41 | function renderChildren(children, domQuery) {
42 | var body = findByTagName(children, 'body');
43 | var head = findByTagName(children, 'head');
44 | var root = VirtualElement();
45 |
46 | root._children = children;
47 | body._parent = null;
48 | body.render(domQuery);
49 |
50 | server.await(function () {
51 | if (head) {
52 | if (server.options.baseTag) {
53 | head.children().splice(0, 0, getBaseTag());
54 | }
55 | }
56 | body.attr('data-blocks-server-data', JSON.stringify(server.data));
57 | server.rendered = root.renderChildren();
58 | });
59 | }
60 |
61 | function findByTagName(children, tagName) {
62 | var result;
63 |
64 | blocks.each(children, function(child) {
65 | if (VirtualElement.Is(child)) {
66 | if (child.tagName() == tagName) {
67 | result = child;
68 | return false;
69 | } else {
70 | result = findByTagName(child.children(), tagName);
71 | }
72 | }
73 | });
74 |
75 | return result;
76 | }
77 |
78 | function getBaseTag() {
79 | var baseUrl = window.location.protocol + '//' + window.location.host + window.__baseUrl__ + '/';
80 | return VirtualElement('base').attr('href', baseUrl);
81 | }
82 |
83 | var executeExpressionValue = Expression.Execute;
84 | var commentRegEx = /^');
125 | }
126 | });
127 |
128 | parser.end(html);
129 |
130 | return root.children();
131 | }
132 |
133 | // TODO: Refactor this because it is duplicate from query/createVirtual.js file
134 | function generateStyleObject(styleString) {
135 | var styles = styleString.split(';');
136 | var styleObject = {};
137 | var index;
138 | var style;
139 | var values;
140 |
141 | for (var i = 0; i < styles.length; i++) {
142 | style = styles[i];
143 | if (style) {
144 | index = style.indexOf(':');
145 | if (index != -1) {
146 | values = [style.substring(0, index), style.substring(index + 1)];
147 | styleObject[values[0].toLowerCase().replace(trimRegExp, '')] = values[1].replace(trimRegExp, '');
148 | }
149 | }
150 | }
151 |
152 | return styleObject;
153 | }
154 |
155 | return parseToVirtual;
156 | });
--------------------------------------------------------------------------------
/src/query.js:
--------------------------------------------------------------------------------
1 | define([
2 | './query/ready',
3 | './query/queries',
4 | './query/observable',
5 | './query/extenders',
6 | './query/DomQuery',
7 | './query/methods'
8 | ]);
9 |
--------------------------------------------------------------------------------
/src/query/ChunkManager.js:
--------------------------------------------------------------------------------
1 | define([
2 | '../core',
3 | './dom',
4 | './animation',
5 | './ElementsData'
6 | ], function (blocks, dom, animation, ElementsData) {
7 | function ChunkManager(observable) {
8 | this.observable = observable;
9 | this.chunkLengths = {};
10 | this.dispose();
11 | }
12 |
13 | ChunkManager.prototype = {
14 | dispose: function () {
15 | this.childNodesCount = undefined;
16 | this.startIndex = 0;
17 | this.observableLength = undefined;
18 | this.startOffset = 0;
19 | this.endOffset = 0;
20 | },
21 |
22 | setStartIndex: function (index) {
23 | this.startIndex = index + this.startOffset;
24 | },
25 |
26 | setChildNodesCount: function (count) {
27 | if (this.childNodesCount === undefined) {
28 | this.observableLength = this.observable._getValue().length;
29 | }
30 | this.childNodesCount = count - (this.startOffset + this.endOffset);
31 | },
32 |
33 | chunkLength: function (wrapper) {
34 | var chunkLengths = this.chunkLengths;
35 | var id = ElementsData.id(wrapper);
36 | var length = chunkLengths[id] || (this.childNodesCount || wrapper.childNodes.length) / (this.observableLength || this.observable._getValue().length);
37 | var result;
38 |
39 | if (blocks.isNaN(length) || length === Infinity) {
40 | result = 0;
41 | } else {
42 | result = Math.round(length);
43 | }
44 |
45 | chunkLengths[id] = result;
46 |
47 | return result;
48 | },
49 |
50 | getAt: function (wrapper, index) {
51 | var chunkLength = this.chunkLength(wrapper);
52 | var childNodes = wrapper.childNodes;
53 | var result = [];
54 |
55 | for (var i = 0; i < chunkLength; i++) {
56 | result[i] = childNodes[index * chunkLength + i + this.startIndex];
57 | }
58 | return result;
59 | },
60 |
61 | insertAt: function (wrapper, index, chunk) {
62 | animation.insert(
63 | wrapper,
64 | this.chunkLength(wrapper) * index + this.startIndex,
65 | blocks.isArray(chunk) ? chunk : [chunk]);
66 | },
67 |
68 | remove: function (index, howMany) {
69 | var _this = this;
70 |
71 | this.each(function (domElement) {
72 | for (var j = 0; j < howMany; j++) {
73 | _this._removeAt(domElement, index);
74 | }
75 | });
76 |
77 | ElementsData.collectGarbage();
78 |
79 | this.dispose();
80 |
81 | this.observable._indexes.splice(index, howMany);
82 | },
83 |
84 | add: function (addItems, index) {
85 | var _this = this;
86 | var observable = this.observable;
87 |
88 | blocks.each(addItems, function (item, i) {
89 | observable._indexes.splice(index + i, 0, blocks.observable(index + i));
90 | });
91 |
92 | this.each(function (domElement, virtualElement) {
93 | var domQuery = blocks.domQuery(domElement);
94 | var context = blocks.context(domElement);
95 | var html = '';
96 | var syncIndex;
97 |
98 | domQuery.contextBubble(context, function () {
99 | syncIndex = domQuery.getSyncIndex();
100 | for (var i = 0; i < addItems.length; i++) {
101 | domQuery.dataIndex(blocks.observable.getIndex(observable, i + index, true));
102 | domQuery.pushContext(addItems[i]);
103 | html += virtualElement.renderChildren(domQuery, syncIndex + (i + index));
104 | domQuery.popContext();
105 | domQuery.dataIndex(undefined);
106 | }
107 | });
108 |
109 | if (domElement.childNodes.length === 0) {
110 | dom.html(domElement, html);
111 | domQuery.createElementObservableDependencies(domElement.childNodes);
112 | } else {
113 | var fragment = domQuery.createFragment(html);
114 | _this.insertAt(domElement, index, fragment);
115 | }
116 | });
117 |
118 | this.dispose();
119 | },
120 | smoothIndexes: function () {
121 | for (var i = 0; i < this.observable._indexes.length; i++) {
122 | this.observable._indexes[i](i);
123 | }
124 | },
125 | each: function (callback) {
126 | var i = 0;
127 | var domElements = this.observable._elements;
128 |
129 | for (; i < domElements.length; i++) {
130 | var data = domElements[i];
131 | if (!data.element) {
132 | data.element = ElementsData.data(data.elementId).dom;
133 | }
134 | this.setup(data.element, callback);
135 | }
136 | },
137 |
138 | setup: function (domElement, callback) {
139 | if (!domElement) {
140 | return;
141 | }
142 |
143 | var eachData = ElementsData.data(domElement).eachData;
144 | var element;
145 | var commentId;
146 | var commentIndex;
147 | var commentElement;
148 |
149 | if (!eachData || eachData.id != this.observable.__id__) {
150 | return;
151 | }
152 |
153 | element = eachData.element;
154 | this.startOffset = eachData.startOffset;
155 | this.endOffset = eachData.endOffset;
156 |
157 | if (domElement.nodeType == 1) {
158 | // HTMLElement
159 | this.setStartIndex(0);
160 | this.setChildNodesCount(domElement.childNodes.length);
161 | callback(domElement, element, domElement);
162 | } else {
163 | // Comment
164 | commentId = ElementsData.id(domElement);
165 | commentElement = domElement.parentNode.firstChild;
166 | commentIndex = 0;
167 | while (commentElement != domElement) {
168 | commentElement = commentElement.nextSibling;
169 | commentIndex++;
170 | }
171 | this.setStartIndex(commentIndex + 1);
172 | while (commentElement && (commentElement.nodeType != 8 || commentElement.nodeValue.indexOf(commentId + ':/blocks') != 1)) {
173 | commentElement = commentElement.nextSibling;
174 | commentIndex++;
175 | }
176 | this.setChildNodesCount(commentIndex - this.startIndex);
177 | callback(domElement.parentNode, element, domElement);
178 | }
179 | },
180 |
181 | _removeAt: function (wrapper, index) {
182 | var chunkLength = this.chunkLength(wrapper);
183 |
184 | animation.remove(
185 | wrapper,
186 | chunkLength * index + this.startIndex,
187 | chunkLength);
188 | }
189 | };
190 | return ChunkManager;
191 | });
192 |
--------------------------------------------------------------------------------
/src/query/ElementsData.js:
--------------------------------------------------------------------------------
1 | define([
2 | './var/dataIdAttr',
3 | './var/virtualElementIdentity',
4 | './VirtualElement'
5 | ], function (dataIdAttr, virtualElementIdentity, VirtualElement) {
6 | var data = {};
7 | var globalId = 1;
8 |
9 | function getDataId(element) {
10 | var result = element ? VirtualElement.Is(element) ? element._state ? element._state.attributes[dataIdAttr] : element._attributes[dataIdAttr] :
11 | element.nodeType == 1 ? element.getAttribute(dataIdAttr) :
12 | element.nodeType == 8 ? /\s+(\d+):[^\/]/.exec(element.nodeValue) :
13 | null :
14 | null;
15 |
16 | return blocks.isArray(result) ? result[1] : result;
17 | }
18 |
19 | function setDataId(element, id) {
20 | if (VirtualElement.Is(element)) {
21 | element.attr(dataIdAttr, id);
22 | } else if (element.nodeType == 1) {
23 | element.setAttribute(dataIdAttr, id);
24 | }
25 | }
26 |
27 | var ElementsData = {
28 | id: function (element) {
29 | return getDataId(element);
30 | },
31 |
32 | /* @if SERVER */
33 | reset: function () {
34 | data = {};
35 | globalId = 1;
36 | },
37 | /* @endif */
38 |
39 | collectGarbage: function () {
40 | blocks.each(data, function (value) {
41 | if (value && value.dom && !document.body.contains(value.dom)) {
42 | ElementsData.clear(value.id, true);
43 | }
44 | });
45 | },
46 |
47 | createIfNotExists: function (element) {
48 | var isVirtual = element && element.__identity__ == virtualElementIdentity;
49 | var currentData;
50 | var id;
51 |
52 | if (isVirtual) {
53 | currentData = data[element._getAttr(dataIdAttr)];
54 | } else {
55 | currentData = data[element && getDataId(element)];
56 | }
57 |
58 | if (!currentData) {
59 | id = globalId++;
60 | if (element) {
61 | if (isVirtual && element._each) {
62 | element._haveAttributes = true;
63 | if (element._state) {
64 | element._state.attributes[dataIdAttr] = id;
65 | } else {
66 | element._attributes[dataIdAttr] = id;
67 | }
68 | } else {
69 | setDataId(element, id);
70 | }
71 | }
72 |
73 | // if element is not defined then treat it as expression
74 | if (!element) {
75 | currentData = data[id] = {
76 | id: id,
77 | observables: {}
78 | };
79 | } else {
80 | currentData = data[id] = {
81 | id: id,
82 | virtual: isVirtual ? element : null,
83 | animating: 0,
84 | observables: {},
85 | preprocess: isVirtual
86 | };
87 | }
88 | }
89 |
90 | return currentData;
91 | },
92 |
93 | byId: function (id) {
94 | return data[id];
95 | },
96 |
97 | data: function (element, name, value) {
98 | var result = data[getDataId(element) || element];
99 | if (!result) {
100 | return;
101 | }
102 | if (arguments.length == 1) {
103 | return result;
104 | } else if (arguments.length > 2) {
105 | result[name] = value;
106 | }
107 | return result[name];
108 | },
109 |
110 | clear: function (element, force) {
111 | var id = getDataId(element) || element;
112 | var currentData = data[id];
113 |
114 | if (currentData && (!currentData.haveData || force)) {
115 | blocks.each(currentData.observables, function (value) {
116 | for (var i = 0; i < value._elements.length; i++) {
117 | if (value._elements[i].elementId == currentData.id) {
118 | value._elements.splice(i, 1);
119 | i--;
120 | }
121 | }
122 |
123 | if (value._expressions) {
124 | for (i = 0; i < value._expressions.length; i++) {
125 | if (value._expressions[i].elementId == currentData.id) {
126 | value._expressions.splice(i, 1);
127 | i--;
128 | }
129 | }
130 | value._expressionKeys[currentData.id] = null;
131 | }
132 | });
133 | data[id] = undefined;
134 | if (VirtualElement.Is(element)) {
135 | element.removeAttr(dataIdAttr);
136 | } else if (element.nodeType == 1) {
137 | element.removeAttribute(dataIdAttr);
138 | }
139 | }
140 | }
141 | };
142 | return ElementsData;
143 | });
144 |
--------------------------------------------------------------------------------
/src/query/Expression.js:
--------------------------------------------------------------------------------
1 | define([
2 | '../core',
3 | './var/parameterQueryCache',
4 | '../modules/Escape',
5 | './ElementsData',
6 | './Observer'
7 | ], function (blocks, parameterQueryCache, Escape, ElementsData, Observer) {
8 | function cloneExpression () {
9 | var element = this.element;
10 | this.element = null;
11 | this.clone = null;
12 | var clone = blocks.clone(this, true);
13 | clone.clone = this.clone = cloneExpression;
14 | this.element = element;
15 | return clone;
16 | }
17 | var Expression = {
18 | Html: 0,
19 | ValueOnly: 2,
20 | Raw: 3,
21 | NodeWise: 4,
22 |
23 | Create: function (text, attributeName, element) {
24 | var index = -1;
25 | var endIndex = 0;
26 | var result = [];
27 | var character;
28 | var startIndex;
29 | var match;
30 |
31 | while (text.length > ++index) {
32 | character = text.charAt(index);
33 |
34 | if (character == '{' && text.charAt(index + 1) == '{') {
35 | startIndex = index + 2;
36 | } else if (character == '}' && text.charAt(index + 1) == '}') {
37 | if (startIndex) {
38 | match = text.substring(startIndex, index);
39 | if (!attributeName) {
40 | match = match
41 | .replace(/&/g, '&')
42 | .replace(/"/g, '"')
43 | .replace(/'/g, '\'')
44 | .replace(/</g, '<')
45 | .replace(/>/g, '>');
46 | }
47 |
48 | character = text.substring(endIndex, startIndex - 2);
49 | if (character) {
50 | result.push({
51 | value: character
52 | });
53 | }
54 |
55 | result.push({
56 | expression: match,
57 | attributeName: attributeName
58 | });
59 |
60 | endIndex = index + 2;
61 | }
62 | startIndex = 0;
63 | }
64 | }
65 |
66 | character = text.substring(endIndex);
67 | if (character) {
68 | result.push({
69 | value: character
70 | });
71 | }
72 |
73 | result.text = text;
74 | result.attributeName = attributeName;
75 | result.element = element;
76 | result.isExpression = true;
77 | result.nodeLength = 0;
78 | result.clone = cloneExpression;
79 | return match ? result : null;
80 | },
81 |
82 | GetValue: function (context, elementData, expression, type) {
83 | var nodeWise = type == Expression.NodeWise;
84 | var value = nodeWise || type == Expression.Raw ? [] : '';
85 | var length = expression.length;
86 | var index = -1;
87 | var chunk;
88 | var lastNodeIndex;
89 | var tempValue;
90 |
91 | type = type||Expression.Html;
92 |
93 | if (!context) {
94 | return expression.text;
95 | }
96 |
97 | if (type !== Expression.ValueOnly) {
98 | expression.nodeLength = 0; // reset for recalculation
99 | }
100 |
101 | if (length == 1) {
102 | if (nodeWise) {
103 | lastNodeIndex = expression.nodeLength;
104 | tempValue = Expression.Execute(context, elementData, expression[0], expression, type);
105 |
106 | if ((expression.nodeLength - lastNodeIndex) == 2) {
107 | value[expression.nodeLength - 2] = null;
108 | }
109 | value[expression.nodeLength - 1] = tempValue;
110 | } else {
111 | value = Expression.Execute(context, elementData, expression[0], expression, type);
112 | }
113 | } else {
114 | while (++index < length) {
115 | lastNodeIndex = expression.nodeLength;
116 | chunk = expression[index];
117 |
118 | if (chunk.value) {
119 | if (type !== Expression.ValueOnly && expression.nodeLength === 0) {
120 | expression.nodeLength++;
121 | }
122 | tempValue = chunk.value;
123 | } else {
124 | tempValue = Expression.Execute(context, elementData, chunk, expression, type);
125 | if (nodeWise && (expression.nodeLength - lastNodeIndex) == 2) {
126 | value[expression.nodeLength - 2] = null; // dom comments that got rendered in the expression
127 | }
128 | }
129 |
130 | if (nodeWise) {
131 | value[expression.nodeLength - 1] = (value[expression.nodeLength - 1] || '') + tempValue;
132 | } else if (type == Expression.Raw) {
133 | value.push(tempValue);
134 | } else {
135 | value += tempValue;
136 | }
137 | }
138 | }
139 |
140 | expression.lastResult = value;
141 |
142 | return value;
143 | },
144 |
145 | Execute: function (context, elementData, expressionData, entireExpression, type) {
146 | var expression = expressionData.expression;
147 | var attributeName = expressionData.attributeName;
148 | var isObservable;
149 | var expressionObj;
150 | var observables;
151 | var result;
152 | var value;
153 | var func;
154 |
155 | // jshint -W054
156 | // Disable JSHint error: The Function constructor is a form of eval
157 | func = parameterQueryCache[expression] = parameterQueryCache[expression] ||
158 | new Function('c', 'with(c){with($this){ return ' + expression + '}}'/*@if DEBUG */ + '//# sourceURL=' + encodeURI(entireExpression.text)/* @endif */);
159 |
160 | Observer.startObserving();
161 |
162 | /* @if DEBUG */ {
163 | try {
164 | value = func(context);
165 | } catch (ex) {
166 | blocks.debug.expressionFail(expression, entireExpression.element);
167 | }
168 | } /* @endif */
169 |
170 | value = func(context);
171 |
172 | isObservable = blocks.isObservable(value);
173 | result = isObservable ? value() : value;
174 | result = result == null ? '' : result.toString();
175 | result = attributeName ? Escape.forHTMLAttributes(result) : Escape.forHTML(result);
176 |
177 | observables = Observer.stopObserving();
178 |
179 | if (type == Expression.Raw) {
180 | return {observables: observables, result: result, value: value};
181 | }
182 |
183 | if (type != Expression.ValueOnly && type != Expression.NodeWise && (isObservable || observables.length)) {
184 | if (!attributeName) {
185 | elementData = ElementsData.createIfNotExists();
186 | }
187 | if (elementData) {
188 | elementData.haveData = true;
189 |
190 | expressionObj = {
191 | length: result.length,
192 | attr: attributeName,
193 | context: context,
194 | elementId: elementData.id,
195 | expression: expression,
196 | entire: entireExpression
197 | };
198 |
199 | blocks.each(observables, function (observable) {
200 | if (!observable._expressionKeys[elementData.id + (attributeName ||'expression') +'[' + expression + ']']) {
201 | observable._expressionKeys[elementData.id + (attributeName ||'expression') +'[' + expression + ']'] = true;
202 | observable._expressions.push(expressionObj);
203 | }
204 |
205 | elementData.observables[observable.__id__ + (attributeName || 'expression') + '[' + expression + ']'] = observable;
206 | });
207 | }
208 | if (!attributeName) {
209 | entireExpression.nodeLength += 2;
210 | result = '' + result;
211 | }
212 | } else if (!attributeName && type !== Expression.ValueOnly) {
213 | if (type == Expression.NodeWise && isObservable) {
214 | entireExpression.nodeLength += 2;
215 | } else if (entireExpression.nodeLength === 0) {
216 | entireExpression.nodeLength++;
217 | }
218 | }
219 |
220 | return result;
221 | }
222 | };
223 |
224 | return Expression;
225 | });
226 |
--------------------------------------------------------------------------------
/src/query/Observer.js:
--------------------------------------------------------------------------------
1 | define(function () {
2 | var Observer = (function () {
3 | var stack = [];
4 |
5 | return {
6 | startObserving: function () {
7 | stack.push([]);
8 | },
9 |
10 | stopObserving: function () {
11 | return stack.pop();
12 | },
13 |
14 | currentObservables: function () {
15 | return stack[stack.length - 1];
16 | },
17 |
18 | registerObservable: function (newObservable) {
19 | var observables = stack[stack.length - 1];
20 | var alreadyExists = false;
21 |
22 | if (observables) {
23 | blocks.each(observables, function (observable) {
24 | if (observable === newObservable) {
25 | alreadyExists = true;
26 | return false;
27 | }
28 | });
29 | if (!alreadyExists) {
30 | observables.push(newObservable);
31 | }
32 | }
33 | }
34 | };
35 | })();
36 |
37 | return Observer;
38 | });
39 |
--------------------------------------------------------------------------------
/src/query/VirtualComment.js:
--------------------------------------------------------------------------------
1 | define([
2 | '../core',
3 | '../var/trimRegExp',
4 | './var/dataIdAttr',
5 | './VirtualElement',
6 | '../modules/Escape'
7 | ], function (blocks, trimRegExp, dataIdAttr, VirtualElement, Escape) {
8 | function VirtualComment(commentText) {
9 | if (!VirtualComment.prototype.isPrototypeOf(this)) {
10 | return new VirtualComment(commentText);
11 | }
12 |
13 | this.__Class__();
14 |
15 | if (commentText.nodeType == 8) {
16 | this._commentText = commentText.nodeValue;
17 | this._el = commentText;
18 | } else {
19 | this._commentText = commentText;
20 | }
21 | }
22 |
23 | blocks.VirtualComment = blocks.inherit(VirtualElement, VirtualComment, {
24 | renderBeginTag: function () {
25 | var dataId = this._getAttr(dataIdAttr);
26 | var html = '';
32 |
33 | return html;
34 | },
35 |
36 | renderEndTag: function () {
37 | var dataId = this._getAttr(dataIdAttr);
38 | var html = '';
44 | return html;
45 | },
46 |
47 | _executeAttributeExpressions: blocks.noop
48 | });
49 |
50 | VirtualComment.Is = function (value) {
51 | return VirtualComment.prototype.isPrototypeOf(value);
52 | };
53 |
54 | return VirtualComment;
55 | });
56 |
--------------------------------------------------------------------------------
/src/query/addListener.js:
--------------------------------------------------------------------------------
1 | define([
2 | '../modules/Event'
3 | ], function (Event) {
4 | function addListener(element, eventName, callback) {
5 | if (element.addEventListener && eventName != 'propertychange') {
6 | element.addEventListener(eventName, function (event) {
7 | callback.call(this, Event.fix(event));
8 | }, false);
9 | } else if (element.attachEvent) {
10 | element.attachEvent('on' + eventName, function (event) {
11 | callback.call(this, Event.fix(event));
12 | });
13 | }
14 | }
15 |
16 | return addListener;
17 | });
18 |
--------------------------------------------------------------------------------
/src/query/browser.js:
--------------------------------------------------------------------------------
1 | define([
2 | '../core'
3 | ], function (blocks) {
4 | var browser = {};
5 |
6 | function parseVersion(matches) {
7 | if (matches) {
8 | return parseFloat(matches[1]);
9 | }
10 | return undefined;
11 | }
12 |
13 | if (typeof document !== 'undefined') {
14 | blocks.extend(browser, {
15 | IE: document && (function () {
16 | var version = 3;
17 | var div = document.createElement('div');
18 | var iElems = div.getElementsByTagName('i');
19 |
20 | /* jshint noempty: false */
21 | // Disable JSHint error: Empty block
22 | // Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment
23 | while (
24 | div.innerHTML = '',
25 | iElems[0]
26 | ) { }
27 | return version > 4 ? version : undefined;
28 | }()),
29 |
30 | Opera: (window && window.navigator && window.opera && window.opera.version && parseInt(window.opera.version(), 10)) || undefined,
31 |
32 | Safari: window && window.navigator && parseVersion(window.navigator.userAgent.match(/^(?:(?!chrome).)*version\/([^ ]*) safari/i)),
33 |
34 | Firefox: window && window.navigator && parseVersion(window.navigator.userAgent.match(/Firefox\/([^ ]*)/))
35 | });
36 | }
37 |
38 | return browser;
39 | });
40 |
--------------------------------------------------------------------------------
/src/query/createFragment.js:
--------------------------------------------------------------------------------
1 | define(function () {
2 | function createFragment(html) {
3 | var fragment = document.createDocumentFragment();
4 | var temp = document.createElement('div');
5 | var count = 1;
6 | var table = '';
8 | var tbody = '';
9 | var tbodyEnd = '';
10 | var tr = '';
11 | var trEnd = '
';
12 |
13 | html = html.toString();
14 |
15 | if ((html.indexOf('