├── .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 | [![jsblocks](http://jsblocks.com/img/logoBeta.png)](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 | * 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 = ''; 7 | var tableEnd = '
'; 8 | var tbody = ''; 9 | var tbodyEnd = ''; 10 | var tr = ''; 11 | var trEnd = ''; 12 | 13 | html = html.toString(); 14 | 15 | if ((html.indexOf(''; 17 | count = 2; 18 | } else if (html.indexOf(''; 33 | 34 | while (count--) { 35 | temp = temp.lastChild; 36 | } 37 | 38 | while (temp.firstChild) { 39 | fragment.appendChild(temp.firstChild); 40 | } 41 | 42 | return fragment; 43 | } 44 | 45 | return createFragment; 46 | }); -------------------------------------------------------------------------------- /src/query/createVirtual.js: -------------------------------------------------------------------------------- 1 | define([ 2 | '../var/trimRegExp', 3 | './var/dataQueryAttr', 4 | './browser', 5 | './Expression', 6 | './VirtualElement', 7 | './VirtualComment', 8 | './serverData' 9 | ], function (trimRegExp, dataQueryAttr, browser, Expression, VirtualElement, VirtualComment, serverData) { 10 | function createVirtual(htmlElement, parentElement) { 11 | var elements = []; 12 | var element; 13 | var tagName; 14 | var elementAttributes; 15 | var htmlAttributes; 16 | var htmlAttribute; 17 | var nodeType; 18 | var commentText; 19 | var commentTextTrimmed; 20 | var data; 21 | 22 | while (htmlElement) { 23 | nodeType = htmlElement.nodeType; 24 | if (nodeType == 1) { 25 | // HtmlDomElement 26 | tagName = htmlElement.tagName.toLowerCase(); 27 | element = new VirtualElement(htmlElement); 28 | element._tagName = tagName; 29 | element._parent = parentElement; 30 | if (parentElement) { 31 | element._each = parentElement._each || parentElement._childrenEach; 32 | } 33 | element._haveAttributes = false; 34 | htmlAttributes = htmlElement.attributes; 35 | elementAttributes = {}; 36 | for (var i = 0; i < htmlAttributes.length; i++) { 37 | htmlAttribute = htmlAttributes[i]; 38 | // the style should not be part of the attributes. The style is handled individually. 39 | if (htmlAttribute.nodeName !== 'style' && 40 | (htmlAttribute.specified || 41 | //IE7 wil return false for .specified for the "value" attribute - WTF! 42 | (browser.IE < 8 && htmlAttribute.nodeName == 'value' && htmlAttribute.nodeValue))) { 43 | elementAttributes[htmlAttribute.nodeName.toLowerCase()] = browser.IE < 11 ? htmlAttribute.nodeValue : htmlAttribute.value; 44 | element._haveAttributes = true; 45 | } 46 | } 47 | element._attributes = elementAttributes; 48 | element._createAttributeExpressions(serverData); 49 | 50 | if (htmlElement.style.cssText) { 51 | element._haveStyle = true; 52 | element._style = generateStyleObject(htmlElement.style.cssText); 53 | } 54 | 55 | setIsSelfClosing(element); 56 | if (tagName == 'script' || tagName == 'style' || tagName == 'code' || element.hasClass('bl-skip')) { 57 | element._innerHTML = htmlElement.innerHTML; 58 | } else { 59 | element._children = createVirtual(htmlElement.childNodes[0], element); 60 | } 61 | 62 | elements.push(element); 63 | } else if (nodeType == 3) { 64 | // TextNode 65 | //if (htmlElement.data.replace(trimRegExp, '').replace(/(\r\n|\n|\r)/gm, '') !== '') { 66 | // 67 | //} 68 | data = htmlElement.data; 69 | elements.push(Expression.Create(data, null, htmlElement) || data); 70 | } else if (nodeType == 8) { 71 | // Comment 72 | commentText = htmlElement.nodeValue; 73 | commentTextTrimmed = commentText.replace(trimRegExp, ''); 74 | if (commentTextTrimmed.indexOf('blocks') === 0) { 75 | element = new VirtualComment(htmlElement); 76 | element._parent = parentElement; 77 | element._attributes[dataQueryAttr] = commentTextTrimmed.substring(6); 78 | data = createVirtual(htmlElement.nextSibling, element); 79 | element._children = data.elements; 80 | element._el._endElement = data.htmlElement; 81 | htmlElement = data.htmlElement || htmlElement; 82 | elements.push(element); 83 | } else if (VirtualComment.Is(parentElement) && commentTextTrimmed.indexOf('/blocks') === 0) { 84 | return { 85 | elements: elements, 86 | htmlElement: htmlElement 87 | }; 88 | } else if (VirtualComment.Is(parentElement)) { 89 | elements.push(''); 90 | } else if (serverData.hasData) { 91 | var number = parseInt(/[0-9]+/.exec(commentTextTrimmed), 10); 92 | if (!blocks.isNaN(number) && serverData.data[number]) { 93 | elements.push(Expression.Create(serverData.data[number])); 94 | } 95 | } else if (commentTextTrimmed.indexOf('/blocks') !== 0) { 96 | elements.push(''); 97 | } 98 | } 99 | htmlElement = htmlElement.nextSibling; 100 | } 101 | return elements; 102 | } 103 | 104 | function generateStyleObject(styleString) { 105 | var styles = styleString.split(';'); 106 | var styleObject = {}; 107 | var index; 108 | var style; 109 | var values; 110 | 111 | for (var i = 0; i < styles.length; i++) { 112 | style = styles[i]; 113 | if (style) { 114 | index = style.indexOf(':'); 115 | if (index != -1) { 116 | values = [style.substring(0, index), style.substring(index + 1)]; 117 | styleObject[values[0].toLowerCase().replace(trimRegExp, '')] = values[1].replace(trimRegExp, ''); 118 | } 119 | } 120 | } 121 | 122 | return styleObject; 123 | } 124 | 125 | var isSelfClosingCache = {}; 126 | function setIsSelfClosing(element) { 127 | var tagName = element._tagName; 128 | var domElement; 129 | 130 | if (isSelfClosingCache[tagName] !== undefined) { 131 | element._isSelfClosing = isSelfClosingCache[tagName]; 132 | return; 133 | } 134 | domElement = document.createElement('div'); 135 | domElement.appendChild(document.createElement(tagName)); 136 | isSelfClosingCache[tagName] = element._isSelfClosing = domElement.innerHTML.indexOf('= 8) { 46 | globalSelectionChangeHandler(element, call); 47 | subscribe('dragend', deferCall); 48 | } 49 | } else { 50 | subscribe('input', call); 51 | 52 | if (browser.Safari < 7 && element.tagName.toLowerCase() == 'textarea') { 53 | subscribe('keydown', deferCall); 54 | subscribe('paste', deferCall); 55 | subscribe('cut', deferCall); 56 | } else if (browser.Opera < 11) { 57 | subscribe('keydown', deferCall); 58 | } else if (browser.Firefox < 4.0) { 59 | subscribe('DOMAutoComplete', call); 60 | subscribe('dragdrop', call); 61 | subscribe('drop', call); 62 | } 63 | } 64 | } 65 | }; 66 | 67 | var globalSelectionChangeHandler = (function () { 68 | var isRegistered = false; 69 | 70 | function selectionChangeHandler(e) { 71 | var element = this.activeElement; 72 | var handler = element && ElementsData.data(element, 'selectionchange'); 73 | if (handler) { 74 | handler(e); 75 | } 76 | } 77 | 78 | return function (element, handler) { 79 | if (!isRegistered) { 80 | addListener(element.ownerDocument, 'selectionchange', selectionChangeHandler); 81 | isRegistered = true; 82 | } 83 | ElementsData.createIfNotExists(element).selectionChange = handler; 84 | }; 85 | })(); 86 | 87 | return on; 88 | }); 89 | -------------------------------------------------------------------------------- /src/query/parseQuery.js: -------------------------------------------------------------------------------- 1 | define([ 2 | '../var/trimRegExp' 3 | ], function (trimRegExp) { 4 | 5 | function parseQuery(query, callback, context) { 6 | var character = 0; 7 | var bracketsCount = 0; 8 | var curlyBracketsCount = 0; 9 | var squareBracketsCount = 0; 10 | var isInSingleQuotes = false; 11 | var isInDoubleQuotes = false; 12 | var startIndex = 0; 13 | var parameters = []; 14 | var currentParameter; 15 | var methodName; 16 | 17 | query = query || ''; 18 | 19 | for (var i = 0; i < query.length; i++) { 20 | character = query.charAt(i); 21 | 22 | if (!isInSingleQuotes && !isInDoubleQuotes) { 23 | if (character == '[') { 24 | squareBracketsCount++; 25 | } else if (character == ']') { 26 | squareBracketsCount--; 27 | } else if (character == '{') { 28 | curlyBracketsCount++; 29 | } else if (character == '}') { 30 | curlyBracketsCount--; 31 | } 32 | } 33 | 34 | if (curlyBracketsCount !== 0 || squareBracketsCount !== 0) { 35 | continue; 36 | } 37 | 38 | if (character == '\'') { 39 | isInSingleQuotes = !isInSingleQuotes; 40 | } else if (character == '"') { 41 | isInDoubleQuotes = !isInDoubleQuotes; 42 | } 43 | 44 | if (isInSingleQuotes || isInDoubleQuotes) { 45 | continue; 46 | } 47 | 48 | if (character == '(') { 49 | if (bracketsCount === 0) { 50 | methodName = query.substring(startIndex, i).replace(trimRegExp, ''); 51 | startIndex = i + 1; 52 | } 53 | bracketsCount++; 54 | } else if (character == ')') { 55 | bracketsCount--; 56 | if (bracketsCount === 0) { 57 | currentParameter = query.substring(startIndex, i).replace(trimRegExp, ''); 58 | if (currentParameter.length) { 59 | parameters.push(currentParameter); 60 | } 61 | 62 | if (methodName) { 63 | methodName = methodName.replace(/^("|')+|("|')+$/g, ''); // trim single and double quotes 64 | if (context) { 65 | callback.call(context, methodName, parameters); 66 | } else { 67 | callback(methodName, parameters); 68 | } 69 | } 70 | parameters = []; 71 | methodName = undefined; 72 | } 73 | } else if (character == ',' && bracketsCount == 1) { 74 | currentParameter = query.substring(startIndex, i).replace(trimRegExp, ''); 75 | if (currentParameter.length) { 76 | parameters.push(currentParameter); 77 | } 78 | startIndex = i + 1; 79 | } else if (character == '.' && bracketsCount === 0) { 80 | startIndex = i + 1; 81 | } 82 | } 83 | } 84 | 85 | return parseQuery; 86 | }); -------------------------------------------------------------------------------- /src/query/ready.js: -------------------------------------------------------------------------------- 1 | define([ 2 | '../core', 3 | '../modules/parseCallback', 4 | '../modules/Events' 5 | ], function (blocks, parseCallback, Events) { 6 | // Implementation of blocks.domReady event 7 | (function () { 8 | blocks.isDomReady = false; 9 | 10 | //blocks.elementReady = function (element, callback, thisArg) { 11 | // callback = parseCallback(callback, thisArg); 12 | // if (element) { 13 | // callback(); 14 | // } else { 15 | // blocks.domReady(callback); 16 | // } 17 | //}; 18 | 19 | blocks.domReady = function (callback, thisArg) { 20 | if (typeof document == 'undefined' || typeof window == 'undefined' || 21 | (window.__mock__ && document.__mock__)) { 22 | return; 23 | } 24 | 25 | callback = parseCallback(callback, thisArg); 26 | if (blocks.isDomReady || document.readyState == 'complete' || 27 | (window.jQuery && window.jQuery.isReady)) { 28 | blocks.isDomReady = true; 29 | callback(); 30 | } else { 31 | Events.on(blocks.core, 'domReady', callback); 32 | handleReady(); 33 | } 34 | }; 35 | 36 | function handleReady() { 37 | if (document.readyState === 'complete') { 38 | setTimeout(ready); 39 | } else if (document.addEventListener) { 40 | document.addEventListener('DOMContentLoaded', completed, false); 41 | window.addEventListener('load', completed, false); 42 | } else { 43 | document.attachEvent('onreadystatechange', completed); 44 | window.attachEvent('onload', completed); 45 | 46 | var top = false; 47 | try { 48 | top = window.frameElement == null && document.documentElement; 49 | } catch (e) { } 50 | 51 | if (top && top.doScroll) { 52 | (function doScrollCheck() { 53 | if (!blocks.isDomReady) { 54 | try { 55 | top.doScroll('left'); 56 | } catch (e) { 57 | return setTimeout(doScrollCheck, 50); 58 | } 59 | 60 | ready(); 61 | } 62 | })(); 63 | } 64 | } 65 | } 66 | 67 | function completed() { 68 | if (document.addEventListener || event.type == 'load' || document.readyState == 'complete') { 69 | ready(); 70 | } 71 | } 72 | 73 | function ready() { 74 | if (!blocks.isDomReady) { 75 | blocks.isDomReady = true; 76 | Events.trigger(blocks.core, 'domReady'); 77 | Events.off(blocks.core, 'domReady'); 78 | } 79 | } 80 | })(); 81 | }); 82 | -------------------------------------------------------------------------------- /src/query/serverData.js: -------------------------------------------------------------------------------- 1 | define([ 2 | ], function () { 3 | var serverData = {hasData: false}; 4 | blocks.domReady(function () { 5 | if (global.document && global.document.body) { 6 | var data = document.body.getAttribute('data-blocks-server-data'); 7 | if (data) { 8 | document.body.removeAttribute('data-blocks-server-data'); 9 | /* global JSON */ 10 | serverData.data = JSON.parse(data.replace('"', '"')); 11 | serverData.hasData = true; 12 | } 13 | } 14 | }); 15 | return serverData; 16 | }); -------------------------------------------------------------------------------- /src/query/setClass.js: -------------------------------------------------------------------------------- 1 | define([ 2 | '../var/trimRegExp', 3 | './var/classAttr', 4 | './getClassIndex', 5 | './VirtualElement' 6 | ], function (trimRegExp, classAttr, getClassIndex, VirtualElement) { 7 | 8 | var classListMultiArguments = true; 9 | if (typeof document !== 'undefined') { 10 | var element = document.createElement('div'); 11 | if (element.classList) { 12 | element.classList.add('a', 'b'); 13 | classListMultiArguments = element.className == 'a b'; 14 | } 15 | } 16 | 17 | function setClass(type, element, classNames) { 18 | if (classNames != null) { 19 | classNames = blocks.isArray(classNames) ? classNames : classNames.toString().split(' '); 20 | var i = 0; 21 | var classAttribute; 22 | var className; 23 | var index; 24 | 25 | if (VirtualElement.Is(element)) { 26 | classAttribute = element._getAttr(classAttr); 27 | } else if (element.classList) { 28 | if (classListMultiArguments) { 29 | element.classList[type].apply(element.classList, classNames); 30 | } else { 31 | blocks.each(classNames, function (value) { 32 | element.classList[type](value); 33 | }); 34 | } 35 | return; 36 | } else { 37 | classAttribute = element.className; 38 | } 39 | classAttribute = classAttribute || ''; 40 | 41 | for (; i < classNames.length; i++) { 42 | className = classNames[i]; 43 | index = getClassIndex(classAttribute, className); 44 | if (type == 'add') { 45 | if (index < 0) { 46 | if (classAttribute !== '') { 47 | className = ' ' + className; 48 | } 49 | classAttribute += className; 50 | } 51 | } else if (index != -1) { 52 | classAttribute = (classAttribute.substring(0, index) + ' ' + 53 | classAttribute.substring(index + className.length + 1, classAttribute.length)).replace(trimRegExp, ''); 54 | } 55 | } 56 | 57 | if (VirtualElement.Is(element)) { 58 | if (element._state) { 59 | element._state.attributes[classAttr] = classAttribute; 60 | } else { 61 | element._attributes[classAttr] = classAttribute; 62 | } 63 | } else { 64 | element.className = classAttribute; 65 | } 66 | } 67 | } 68 | 69 | return setClass; 70 | }); 71 | -------------------------------------------------------------------------------- /src/query/var/OBSERVABLE.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | return '__blocks.observable__'; 3 | }); -------------------------------------------------------------------------------- /src/query/var/classAttr.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | return 'class'; 3 | }); -------------------------------------------------------------------------------- /src/query/var/dataIdAttr.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | return 'data-id'; 3 | }); -------------------------------------------------------------------------------- /src/query/var/dataQueryAttr.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | return 'data-query'; 3 | }); -------------------------------------------------------------------------------- /src/query/var/parameterQueryCache.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | return {}; 3 | }); -------------------------------------------------------------------------------- /src/query/var/queries.js: -------------------------------------------------------------------------------- 1 | define([ 2 | '../../core' 3 | ], function (blocks) { 4 | /** 5 | * @typedef {Object} blocks.query 6 | * @property {boolean} [prioritize] - If set the query will be executed before all other queries on the element, except other prioritized quieries. 7 | * @property {boolean} [passRawValues] - If set observables will not be unwraped. 8 | * @property {boolean} [passDomQuery] - If set the current DomQuery will be passed as the first argument. Usefull to execute other queries on the element. See blocks.queries.if for an example. 9 | * @property {boolean} [supportsComments] - Specifies if the domQuery can be executed on comment nodes. 10 | * @property {Object.} [passRaw] - Specifies if an argument should be evaluated to it's value or passed as the string. The key is the index of the argument. 11 | * @property {Function} [preprocess] - This function will be executed when the element is rendered the first time. The context ``this`` wil be the VirtualElement that get's rendered. 12 | * @property {Function} [ready] - This function will be executed on the dom element when the element got rendered. The context ``this`` will be corresponding the dom node. 13 | * @property {Function} [update] - This function will be executed on the dom element each time an observable passed as an argument changed. The context will be the corresponding dom node. 14 | */ 15 | /** 16 | * @type {Object} 17 | */ 18 | return (blocks.queries = {}); 19 | }); -------------------------------------------------------------------------------- /src/query/var/virtualElementIdentity.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | return '__blocks.VirtualElement__'; 3 | }); -------------------------------------------------------------------------------- /src/var/hasOwn.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | return Object.prototype.hasOwnProperty; 3 | }); -------------------------------------------------------------------------------- /src/var/identity.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | return function (value) { 3 | return value; 4 | }; 5 | }); -------------------------------------------------------------------------------- /src/var/slice.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | return Array.prototype.slice; 3 | }); -------------------------------------------------------------------------------- /src/var/strundefined.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | return typeof undefined; 3 | }); 4 | -------------------------------------------------------------------------------- /src/var/support.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | return {}; 3 | }); -------------------------------------------------------------------------------- /src/var/toString.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | return Object.prototype.toString; 3 | }); -------------------------------------------------------------------------------- /src/var/trimRegExp.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | return /^\s+|\s+$/gm; 3 | }); -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | //Do not use {a} as a constructor 3 | "-W053": false, 4 | 5 | "eqeqeq": false, 6 | "forin": false, 7 | "plusplus": false, 8 | "strict": false, 9 | "newcap": false, 10 | 11 | "es3": true, 12 | "bitwise": true, 13 | "camelcase": true, 14 | "curly": true, 15 | "freeze": true, 16 | "immed": true, 17 | "latedef": true, 18 | "noarg": true, 19 | "noempty": true, 20 | "nonbsp": true, 21 | "nonew": true, 22 | "undef": true, 23 | "unused": true, 24 | "trailing": true, 25 | "loopfunc": true, 26 | "eqnull": true, 27 | "quotmark": "single", 28 | 29 | "browser": true, 30 | 31 | "globals": { 32 | "blocks": true, 33 | "jQuery": true, 34 | "$": true, 35 | 36 | "setFixtures": true, 37 | "runts": true, 38 | "waits": true, 39 | "beforeEach": true, 40 | "afterEach": true, 41 | "describe": true, 42 | "expect": true, 43 | "it": true, 44 | "spyOn": true 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/Runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Jasmine Spec Runner v2.2.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 |
30 |
31 | Run All Tests 32 | 33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /test/pages/input.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 13 | 14 |
15 |

Input, password, search, textarea and expressions update

16 | 17 | My name is: {{value}} 18 |
19 |
20 | {{value}} 21 |
22 | {{value}} 23 |
24 | {{value}} 25 |
26 | 27 |
28 | 29 |
30 | 31 |
32 | 34 |
35 | 36 |
37 |

Range

38 | 39 | {{range}} 40 |
41 | 42 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /test/pages/parsing-each.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 19 | 20 |
17 | 18 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /test/pages/parsing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 13 | 14 | 15 |

{{title}}

16 | 17 |
18 | 19 | 25 | 26 | 40 |
41 | All this should be green and 22px. 42 |
43 | 44 |
45 | 46 |
47 | Text <content> should be scaped! 48 |
quote - " 49 |
single quote - ' 50 |
escaped quote - " 51 |
escaped single quote - ' 52 |
ampersand - & 53 |
54 | 55 |

Parsing iframe with 'src' provided

56 | 57 |

Parsing iframe with 'src' provided and contents inside

58 | 61 |

Parsing iframe only with contents inside

62 | 65 | 66 | 67 |
68 |
69 | 70 |

Parsing empty svg

71 | 72 | 73 |

Parsing full svg

74 | 75 | 76 | 77 | 78 | 79 | 80 | {{svg}} 81 | 82 | 83 |
84 |
85 | 86 |

Parsing canvas element

87 | 88 | 89 | 90 | 91 |

Parsing non-existant element type

92 | 93 | {{title}} 94 | 95 | 96 | {{title}} 97 | 98 | 99 | 102 | 103 | 104 | 105 | 106 | 107 | 109 | 111 | 112 | 113 | 114 |
115 |
116 |
117 |
118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /test/pages/styles.css: -------------------------------------------------------------------------------- 1 | .section { 2 | position: relative; 3 | padding: 50px; 4 | padding-top: 70px; 5 | border: 2px solid gray; 6 | margin-bottom: 50px; 7 | } 8 | 9 | .section h3 { 10 | position: absolute; 11 | top: 0; 12 | } 13 | 14 | .mega-button { 15 | margin: 0 auto; 16 | padding: 20px; 17 | margin: 40px; 18 | } -------------------------------------------------------------------------------- /test/pages/two-way-data-binding.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 19 | 20 | 21 | 22 | 23 |
24 |

input[type="text]

25 | 26 |
27 | 28 |
29 | {{text}}{{text}} 30 |
31 | 32 |
33 | 34 |
35 |

textarea

36 | 37 |
38 | 39 |
40 |
41 | 42 |
43 |

input[type="checkbox"]

44 | 45 | 46 |
47 | 48 | 49 |
50 | {{initialTrue}}{{initialTrue}} 51 |
52 | {{initialFalse}}{{initialFalse}} 53 |
54 | 55 |
56 |

input[type="radio"]

57 | 58 | 59 |
60 | 61 | 62 |
63 | {{initialTrue}} {{initialTrue}} 64 |
65 | {{initialFalse}} {{initialFalse}} 66 |
67 | 68 |
69 |

input[type="radio", value="specified"]

70 | 71 | 72 | 73 |
74 | 75 | 76 | 77 |
78 | {{number}} 79 |
80 | 81 |
82 |

select[data-query="options()"]

83 | No update: 84 | 87 |
88 | Update: 89 | 92 |
93 | {{number}} 94 |
95 | 96 |
97 |

select[data-query="each()"]

98 | No update: 99 | 102 |
103 | Update: 104 | 107 |
108 | Update (data-query=each()): 109 |
110 | 113 |
114 |
115 | 116 |
117 |

select (created manually)

118 | 125 |
126 | 127 |
128 |

select[multiple]

129 | 132 | 135 |
    136 |
  • {{$this}}
  • 137 |
138 |
139 |
140 | 141 |
142 |

select (with optgroup)

143 | 146 |
147 | 148 | -------------------------------------------------------------------------------- /test/runner-styles.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | 6 | .header { 7 | background: gray; 8 | } 9 | 10 | .header .content { 11 | display: inline-block; 12 | margin: 0 auto; 13 | font-size: 24px; 14 | font-weight: bold; 15 | padding: 5px; 16 | } 17 | 18 | .specs, .specs ul { 19 | list-style-type: none; 20 | list-style-position: inside; 21 | } 22 | 23 | .specs > li > ul.group { 24 | float: left; 25 | } 26 | 27 | .specs > li > ul > .group-title { 28 | font-size: 24px; 29 | } 30 | 31 | .specs input { 32 | margin-left: 20px; 33 | } 34 | 35 | .specs .line { 36 | display: inline-block; 37 | padding: 3px; 38 | } 39 | 40 | .specs .selected .line { 41 | background: #51b20a; 42 | } 43 | 44 | .specs .group { 45 | list-style-type: none; 46 | } 47 | 48 | .specs .group-title { 49 | font-weight: bold; 50 | font-style: italic; 51 | font-size: 18px; 52 | } 53 | 54 | .specs .test { 55 | list-style-type: circle; 56 | } -------------------------------------------------------------------------------- /test/spec/dataSource/configuration.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | /*global describe, blocks, it, expect */ 4 | 5 | var testing = blocks.testing; 6 | 7 | describe('blocks.DataSource (Configuration) ->', function () { 8 | it('baseUrl url is appended to read data operation url', function () { 9 | var dataSource = new blocks.DataSource({ 10 | baseUrl: 'base/', 11 | read: { 12 | url: 'read' 13 | } 14 | }); 15 | 16 | var isRequestCalled = false; 17 | testing.overrideAjax({ 18 | 'base/read': function () { 19 | isRequestCalled = true; 20 | } 21 | }); 22 | 23 | dataSource.read(); 24 | expect(isRequestCalled).toBe(true); 25 | 26 | testing.restoreAjax(); 27 | }); 28 | 29 | it('baseUrl url is appended to read data operation url (when fetching)', function () { 30 | var dataSource = new blocks.DataSource({ 31 | baseUrl: 'base/', 32 | read: { 33 | url: 'read' 34 | } 35 | }); 36 | 37 | var isRequestCalled = false; 38 | testing.overrideAjax({ 39 | 'base/read': function () { 40 | isRequestCalled = true; 41 | } 42 | }); 43 | 44 | dataSource.read(); 45 | expect(isRequestCalled).toBe(true); 46 | 47 | testing.restoreAjax(); 48 | }); 49 | 50 | it('baseUrl url is appended to create operation url', function () { 51 | var dataSource = new blocks.DataSource({ 52 | autoSync: true, 53 | baseUrl: 'base/', 54 | create: { 55 | url: 'create' 56 | } 57 | }); 58 | 59 | var isRequestCalled = false; 60 | testing.overrideAjax({ 61 | 'base/create': function () { 62 | isRequestCalled = true; 63 | } 64 | }); 65 | 66 | dataSource.data.add({ 67 | FirstName: 'Antonio' 68 | }); 69 | expect(isRequestCalled).toBe(true); 70 | 71 | testing.restoreAjax(); 72 | }); 73 | 74 | it('baseUrl url is appended to destroy operation url', function () { 75 | var dataSource = new blocks.DataSource({ 76 | baseUrl: 'base/', 77 | destroy: { 78 | url: 'destroy' 79 | } 80 | }); 81 | 82 | var isRequestCalled = false; 83 | testing.overrideAjax({ 84 | 'base/destroy': function () { 85 | isRequestCalled = true; 86 | } 87 | }); 88 | 89 | dataSource.data.add({ 90 | Id: 1 91 | }); 92 | dataSource.sync(); 93 | dataSource.data.remove(dataSource.data.first()); 94 | dataSource.sync(); 95 | expect(isRequestCalled).toBe(true); 96 | 97 | testing.restoreAjax(); 98 | }); 99 | 100 | it('baseUrl url is appended to update operation url', function () { 101 | var dataSource = new blocks.DataSource({ 102 | baseUrl: 'base/', 103 | update: { 104 | url: 'update' 105 | } 106 | }); 107 | 108 | var isRequestCalled = false; 109 | testing.overrideAjax({ 110 | 'base/update': function () { 111 | isRequestCalled = true; 112 | } 113 | }); 114 | 115 | dataSource.data.add({ 116 | Id: 1 117 | }); 118 | dataSource.update(1, { 119 | FirstName: 'FirstName' 120 | }); 121 | dataSource.sync(); 122 | expect(isRequestCalled).toBe(true); 123 | 124 | testing.restoreAjax(); 125 | }); 126 | 127 | it('data could be set from configuration', function () { 128 | var dataSource = new blocks.DataSource({ 129 | data: testing.createStaticData() 130 | }); 131 | expect(dataSource.data().length).toBe(2); 132 | expect(dataSource.data.first().FirstName).toBe('Antonio'); 133 | }); 134 | }); 135 | })(); -------------------------------------------------------------------------------- /test/spec/mvc/application.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var testing = blocks.testing; 3 | 4 | describe('blocks.Application: ', function () { 5 | var Application; 6 | beforeEach(function () { 7 | Application = blocks.Application(); 8 | testing.overrideApplicationStart(Application); 9 | }); 10 | afterEach(function () { 11 | blocks.core.deleteApplication(); 12 | //testing.restoreApplicationStart(Application); 13 | }); 14 | 15 | it('baseUrl is set correctly', function () { 16 | blocks.core.deleteApplication(); 17 | Application = blocks.Application({ 18 | baseUrl: 'test' 19 | }); 20 | testing.overrideApplicationStart(Application); 21 | expect(Application.options.baseUrl).toBe('test'); 22 | expect(Application.options.history).toBe(true); 23 | }); 24 | 25 | it('propertyDefaultOptions exists', function () { 26 | expect(Application.Property.Defaults).toBeDefined(); 27 | }); 28 | 29 | it('options exists', function () { 30 | expect(Application.options).toBeDefined(); 31 | }); 32 | 33 | it('collectionDefaultOptions exists', function () { 34 | expect(Application.Collection.Defaults).toBeDefined(); 35 | }); 36 | 37 | it('viewDefaultOptions exists', function () { 38 | expect(Application.View.Defaults).toBeDefined(); 39 | }); 40 | 41 | it('application does not raise errors when start() is called more than once', function () { 42 | Application.start(); 43 | Application.start(); 44 | Application.start(); 45 | }); 46 | }); 47 | 48 | })(); 49 | -------------------------------------------------------------------------------- /test/spec/mvc/collection.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var testing = blocks.testing; 3 | 4 | describe('blocks.Application.Collection: ', function () { 5 | var Application; 6 | var Products; 7 | var Remotes; 8 | var Product; 9 | 10 | beforeEach(function () { 11 | Application = blocks.Application(); 12 | testing.overrideApplicationStart(Application); 13 | 14 | testing.overrideAjax({ 15 | 'Products': function () { 16 | return testing.createStaticData(); 17 | } 18 | }); 19 | 20 | Product = Application.Model({ 21 | FirstName: Application.Property() 22 | }); 23 | 24 | Products = Application.Collection(Product, { 25 | options: { 26 | read: { 27 | url: 'Products' 28 | }, 29 | update: { 30 | 31 | }, 32 | destroy: { 33 | 34 | }, 35 | create: { 36 | 37 | } 38 | }, 39 | 40 | customProperty: function () { 41 | return 'content'; 42 | } 43 | }); 44 | 45 | Remotes = Application.Collection({ 46 | options: { 47 | read: { 48 | 49 | } 50 | } 51 | }); 52 | 53 | Application.start(); 54 | }); 55 | 56 | afterEach(function () { 57 | blocks.core.deleteApplication(); 58 | //testing.restoreApplicationStart(Application); 59 | testing.restoreAjax(); 60 | }); 61 | 62 | describe('read()', function () { 63 | it('returns the collection object', function () { 64 | var products = Products(); 65 | expect(products.read()).toBe(products); 66 | }); 67 | 68 | it('retrieves all data', function () { 69 | var products = Products(); 70 | products.read(); 71 | expect(products().length).toBe(2); 72 | expect(products.at(0).get('FirstName')).toBe('Antonio'); 73 | }); 74 | 75 | it('without calling read() data is not selected', function () { 76 | var products = Products(); 77 | expect(products().length).toBe(0); 78 | }); 79 | 80 | it('repopulates the array with the original items when called', function () { 81 | var products = Products(); 82 | products.read(); 83 | products.reset(products.filter(function (value) { 84 | return value.FirstName() != 'Antonio'; 85 | })); 86 | products.read(); 87 | expect(products().length).toBe(2); 88 | expect(products.at(0).get('FirstName')).toBe('Antonio'); 89 | }); 90 | 91 | it('changes are automatically cleared after read repopulation', function () { 92 | var products = Products(); 93 | products.read(); 94 | products.add({ 95 | FirstName: 'Test' 96 | }); 97 | expect(products.hasChanges()).toBe(true); 98 | 99 | products.read(); 100 | expect(products.hasChanges()).toBe(false); 101 | }); 102 | 103 | it('accepts parameters which are passed to the ajax request', function () { 104 | 105 | }); 106 | 107 | it('accepts a callback function as last parameter after the additional parameters', function () { 108 | 109 | }); 110 | 111 | it('accepts a callback as only parameter', function () { 112 | 113 | }); 114 | }); 115 | 116 | describe('clearChanges()', function () { 117 | it('returns the collection object', function () { 118 | var products = Products(); 119 | expect(products.clearChanges()).toBe(products); 120 | }); 121 | 122 | it('calls dataSource.clearChanges()', function () { 123 | // TODO: .... 124 | }); 125 | }); 126 | 127 | describe('hasChanges()', function () { 128 | it('is observable', function () { 129 | var products = Products(); 130 | expect(blocks.isObservable(products.hasChanges)).toBe(true); 131 | }); 132 | 133 | it('when no changes hasChanges returns false', function () { 134 | var products = Products(); 135 | expect(products.hasChanges()).toBe(false); 136 | products.read(); 137 | expect(products.hasChanges()).toBe(false); 138 | }); 139 | 140 | it('equals dataSource.hasChanges', function () { 141 | var products = Products(); 142 | expect(products._dataSource.hasChanges).toBe(products.hasChanges); 143 | }); 144 | }); 145 | 146 | describe('sync()', function () { 147 | it('returns the collection object', function () { 148 | var products = Products(); 149 | expect(products.sync()).toBe(products); 150 | }); 151 | 152 | it('calls dataSource.sync() method', function () { 153 | 154 | }); 155 | }); 156 | 157 | describe('reset()', function () { 158 | it('reset returns the collection object', function () { 159 | var products = Products(); 160 | expect(products.reset()).toBe(products); 161 | }); 162 | 163 | it('changes the collection successfully', function () { 164 | var products = Products(); 165 | products.read(); 166 | expect(products().length).toBe(2); 167 | products.reset(products.filter(function (value) { 168 | return value.FirstName() != 'Antonio'; 169 | })); 170 | expect(products().length).toBe(1); 171 | expect(products.at(0).get('FirstName')).toBe('Mihaela'); 172 | }); 173 | 174 | it('with no parameters clears the entire collection', function () { 175 | var products = Products(); 176 | products.read(); 177 | products.reset(); 178 | expect(products().length).toBe(0); 179 | }); 180 | }); 181 | 182 | describe('add()', function () { 183 | it('adding dataItems with different properties does not throw exception', function () { 184 | var products = Products(); 185 | products.read(); 186 | 187 | products.add({ 188 | NonExistentTillNowField: true 189 | }, { 190 | AnotherNonExistentFieldTillNow: true 191 | }); 192 | }); 193 | }); 194 | 195 | it('could initialize a initial data', function () { 196 | var products = Products([{}, {}]); 197 | expect(products().length).toBe(2); 198 | }); 199 | 200 | it('the initial data is converted to models', function () { 201 | var products = Products([{ price: 1 }, { price: 2 }, { price: 3 }]); 202 | var model = products()[0]; 203 | expect(blocks.isFunction(model.dataItem)).toBe(true); 204 | expect(blocks.isFunction(model.validate)).toBe(true); 205 | expect(blocks.isFunction(model.isNew)).toBe(true); 206 | expect(blocks.isFunction(model.sync)).toBe(true); 207 | }); 208 | 209 | it('could initialize a initial data from Application.Collection.Product() type of initialization', function () { 210 | var products = Products([{}, {}]); 211 | expect(products().length).toBe(2); 212 | }); 213 | 214 | it('supports custom properties', function () { 215 | var products = Products(); 216 | expect(products.customProperty()).toBe('content'); 217 | }); 218 | 219 | it('methods from an observable are available', function () { 220 | 221 | }); 222 | 223 | it('methods from an observable work correctly', function () { 224 | 225 | }); 226 | 227 | it('methods from expressions are available', function () { 228 | 229 | }); 230 | 231 | it('methods from expressions work correctly', function () { 232 | 233 | }); 234 | }); 235 | })(); 236 | -------------------------------------------------------------------------------- /test/spec/mvc/history.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var testing = blocks.testing; 3 | 4 | function getCurrentUrl() { 5 | return window.location.href.substring(window.location.host.length); 6 | } 7 | 8 | describe('History', function () { 9 | var navigateChangesCount = 0; 10 | 11 | function getHistory(options) { 12 | var Application = blocks.Application({ 13 | options: options 14 | }); 15 | 16 | testing.overrideApplicationStart(Application); 17 | Application.start(); 18 | var history = Application._history; 19 | blocks.core.deleteApplication(); 20 | //testing.restoreApplicationStart(Application); 21 | 22 | $('#testElement').append($('', { id: 'link' })); 23 | 24 | var navigate = history.navigate; 25 | history.navigate = function (fragment, options) { 26 | navigate.call(this, fragment, options); 27 | if (this._use == 'pushState' && (!options || !options.replace)) { 28 | navigateChangesCount++; 29 | } 30 | }; 31 | 32 | history.dispose = function () { 33 | history._onUrlChanged = function () { 34 | }; 35 | }; 36 | 37 | return history; 38 | } 39 | 40 | function changeHash(url) { 41 | if (url.charAt(0) != '#') { 42 | url = '#' + url; 43 | } 44 | $('#link').attr('href', url)[0].click(); 45 | navigateChangesCount++; 46 | } 47 | 48 | beforeEach(function () { 49 | navigateChangesCount = 0; 50 | }); 51 | 52 | afterEach(function () { 53 | if (navigateChangesCount) { 54 | window.history.go(-navigateChangesCount); 55 | } 56 | }); 57 | 58 | 59 | //describe('[hash]', function () { 60 | // describe('navigate', function () { 61 | 62 | // }); 63 | 64 | // describe('urlChange', function () { 65 | // it('is event called', function () { 66 | // var called = false; 67 | // var history = getHistory(); 68 | // history.on('urlChange', function () { 69 | // called = true; 70 | // }); 71 | 72 | // changeHash('#newUrl'); 73 | 74 | // waits(100); 75 | 76 | // runs(function () { 77 | // expect(called).toBe(true); 78 | // history.dispose(); 79 | // }); 80 | // }); 81 | // }); 82 | //}); 83 | 84 | //describe('[pushState]', function () { 85 | 86 | // describe('navigate', function () { 87 | 88 | // }); 89 | 90 | // describe('urlChange', function () { 91 | // it('is called', function () { 92 | // var history = getHistory({ 93 | // history: 'pushState' 94 | // }); 95 | // var called = false; 96 | // history.on('urlChange', function () { 97 | // called = true; 98 | // }); 99 | // history.navigate('/newUrl'); 100 | 101 | // waits(100); 102 | 103 | // runs(function () { 104 | // expect(called).toBe(true); 105 | // history.dispose(); 106 | // }); 107 | // }); 108 | // }); 109 | //}); 110 | }); 111 | })(); 112 | -------------------------------------------------------------------------------- /test/spec/mvc/view.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var testing = blocks.testing; 3 | 4 | describe('blocks.Application.View: ', function () { 5 | var Application; 6 | var Product; 7 | var Products; 8 | 9 | beforeEach(function () { 10 | Application = blocks.Application(); 11 | testing.overrideApplicationStart(Application); 12 | 13 | Product = Application.Model({ 14 | FirstName: Application.Property(), 15 | LastName: Application.Property(), 16 | Age: Application.Property() 17 | }); 18 | 19 | Products = Application.Collection(Product, { 20 | 21 | }); 22 | }); 23 | afterEach(function () { 24 | blocks.core.deleteApplication(); 25 | //testing.restoreApplicationStart(Application); 26 | }); 27 | 28 | it('is active by default', function () { 29 | Application.View('Products', { 30 | 31 | }); 32 | 33 | $('#testElement').attr('data-query', 'text(Products.isActive)'); 34 | 35 | Application.start(); 36 | 37 | 38 | expect($('#testElement')).toHaveText('true'); 39 | }); 40 | 41 | it('correctly binds', function () { 42 | Application.View('Products', { 43 | init: function () { 44 | this.products = Products(testing.createStaticData()); 45 | } 46 | }); 47 | 48 | $('#testElement').append($('
    ', { 49 | 'data-query': 'each(Products.products)' 50 | }).append($('
  • ', { 51 | 'data-query': 'text(FirstName + City)' 52 | }))); 53 | 54 | Application.start(); 55 | 56 | var $ul = $('#testElement').children(); 57 | expect($ul.children().length).toBe(2); 58 | expect($ul.children().eq(0)).toHaveText('AntonioBlagoevgrad'); 59 | expect($ul.children().eq(1)).toHaveText('MihaelaSofia'); 60 | }); 61 | 62 | it('creating View with nothing inside does not throw error', function () { 63 | 64 | }); 65 | 66 | describe('blocks.queries.view', function () { 67 | 68 | }); 69 | }); 70 | })(); 71 | -------------------------------------------------------------------------------- /test/spec/query/extenders.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | describe('blocks.observable.skip', function () { 4 | it('using a primitive value', function () { 5 | var items = blocks.observable([1, 2, 3, 4, 5, 6]).extend('skip', 2); 6 | expect(items()).toEqual([1, 2, 3, 4, 5, 6]); 7 | expect(items.view()).toEqual([3, 4, 5, 6]); 8 | }); 9 | 10 | it('using an observable', function () { 11 | var skipCount = blocks.observable(1); 12 | var items = blocks.observable([1, 2, 3, 4, 5, 6]).extend('skip', skipCount); 13 | expect(items()).toEqual([1, 2, 3, 4, 5, 6]); 14 | 15 | //expect(items.view()).toEqual([2, 3, 4, 5, 6]); 16 | 17 | skipCount(0); 18 | 19 | //expect(items.view()).toEqual([1, 2, 3, 4, 5, 6]); 20 | 21 | skipCount(2); 22 | 23 | expect(items.view()).toEqual([3, 4, 5, 6]); 24 | }); 25 | 26 | it('observable correctly updates the skip', function () { 27 | var skipCount = blocks.observable(100); 28 | var items = blocks.observable([1, 2, 3, 4, 5, 6]).extend('skip', skipCount); 29 | expect(items()).toEqual([1, 2, 3, 4, 5, 6]); 30 | expect(items.view()).toEqual([]); 31 | 32 | skipCount(0); 33 | 34 | //expect(items.view()).toEqual([1, 2, 3, 4, 5, 6]); 35 | 36 | skipCount(100); 37 | 38 | expect(items.view()).toEqual([]); 39 | }); 40 | }); 41 | 42 | describe('blocks.observable.take', function () { 43 | it('using primitive value', function () { 44 | var items = blocks.observable([1, 2, 3, 4, 5, 6]).extend('take', 3); 45 | expect(items()).toEqual([1, 2, 3, 4, 5, 6]); 46 | expect(items.view()).toEqual([1, 2, 3]); 47 | }); 48 | 49 | it('using observable and updates correctly', function () { 50 | var takeCount = blocks.observable(1); 51 | var items = blocks.observable([1, 2, 3, 4, 5, 6]).extend('take', takeCount); 52 | expect(items()).toEqual([1, 2, 3, 4, 5, 6]); 53 | //expect(items.view()).toEqual([1]); 54 | 55 | takeCount(0); 56 | 57 | //expect(items.view()).toEqual([]); 58 | 59 | takeCount(100); 60 | 61 | expect(items.view()).toEqual([1, 2, 3, 4, 5, 6]); 62 | }); 63 | 64 | it('provide value bigger than the collection takes all the items', function () { 65 | var items = blocks.observable([1, 2, 3, 4, 5, 6]).extend('take', 100); 66 | expect(items()).toEqual([1, 2, 3, 4, 5, 6]); 67 | expect(items.view()).toEqual([1, 2, 3, 4, 5, 6]); 68 | }); 69 | }); 70 | 71 | describe('blocks.observable.sort', function () { 72 | // TODO: Default sorting should sort numbers correctly 73 | //it('default sorting', function () { 74 | // var items = blocks.observable([5, 3, 7, 1, 9, 11, 2]).extend('sort'); 75 | // expect(items()).toEqual([5, 3, 7, 1, 9, 11, 2]); 76 | // expect(items.view()).toEqual([1, 2, 3, 5, 7, 9, 11]); 77 | //}); 78 | 79 | it('sort by field', function () { 80 | var data = [ 81 | { id: 5 }, 82 | { id: 3 }, 83 | { id: 7 }, 84 | { id: 1 } 85 | ]; 86 | var items = blocks.observable(data).extend('sort', 'id'); 87 | 88 | expect(items()).toEqual(data); 89 | expect(items.view()).toEqual([ 90 | { id: 1 }, 91 | { id: 3 }, 92 | { id: 5 }, 93 | { id: 7 } 94 | ]); 95 | }); 96 | }); 97 | 98 | describe('blocks.observable.filter', function () { 99 | it('filter default by providng observable', function () { 100 | var filterValue = blocks.observable(); 101 | var items = blocks.observable([]).extend('filter', filterValue); 102 | }); 103 | }); 104 | })(); -------------------------------------------------------------------------------- /test/spec/query/public-methods.js: -------------------------------------------------------------------------------- 1 | (function (undefined) { 2 | 3 | function initializeFixtures(tagName, attributes) { 4 | tagName = tagName || 'div'; 5 | attributes = attributes || {}; 6 | var fixture = $('
    ', { 7 | id: 'sandbox' 8 | }); 9 | fixture.append($('<' + tagName + '>', blocks.extend({ 10 | id: 'testElement' 11 | }, attributes))); 12 | setFixtures(fixture); 13 | } 14 | 15 | function query(model) { 16 | blocks.query(model || {}, document.getElementById('sandbox')); 17 | } 18 | 19 | describe('blocks.unwrapObservable()', function () { 20 | it('should return the same value when the value passed is not an observable', function () { 21 | var result = blocks.unwrapObservable(32); 22 | expect(result).toBe(32); 23 | }); 24 | 25 | it('should return null when null is passed', function () { 26 | var result = blocks.unwrapObservable(null); 27 | expect(result).toBe(null); 28 | }); 29 | 30 | it('should return undefined when undefined is passed', function () { 31 | var result = blocks.unwrapObservable(undefined); 32 | expect(result).toBe(undefined); 33 | }); 34 | 35 | it('should return the observable value when an observable is passed', function () { 36 | var result = blocks.unwrapObservable(blocks.observable(0)); 37 | expect(result).toBe(0); 38 | }); 39 | 40 | it('should return the function result when the observable is a function', function () { 41 | var func = function () { 42 | return 0; 43 | }, 44 | result = blocks.unwrapObservable(blocks.observable(func)); 45 | expect(result).toBe(0); 46 | }); 47 | 48 | it('should return empty string when the observable is empty string', function () { 49 | var result = blocks.unwrapObservable(blocks.observable('')); 50 | expect(result).toBe(''); 51 | }); 52 | 53 | it('should return null when the observable value is null', function () { 54 | var result = blocks.unwrapObservable(blocks.observable(null)); 55 | expect(result).toBe(null); 56 | }); 57 | 58 | it('should return undefined when the observable value is undefined', function () { 59 | var result = blocks.unwrapObservable(blocks.observable(undefined)); 60 | expect(result).toBe(undefined); 61 | }); 62 | 63 | it('should return the original object when __identity__ value is supplied', function () { 64 | var obj = { __identity__: 'observable' }; 65 | var result = blocks.unwrapObservable(obj); 66 | expect(result).toBe(obj); 67 | }); 68 | }); 69 | 70 | describe('blocks.isObservable()', function () { 71 | it('isObservable() = true (Primitive types)', function () { 72 | expect(blocks.isObservable(blocks.observable(3))).toBe(true); 73 | expect(blocks.isObservable(blocks.observable(null))).toBe(true); 74 | expect(blocks.isObservable(blocks.observable(undefined))).toBe(true); 75 | expect(blocks.isObservable(blocks.observable(true))).toBe(true); 76 | expect(blocks.isObservable(blocks.observable(false))).toBe(true); 77 | expect(blocks.isObservable(blocks.observable(new Date()))).toBe(true); 78 | expect(blocks.isObservable(blocks.observable(new Object()))).toBe(true); 79 | expect(blocks.isObservable(blocks.observable({}))).toBe(true); 80 | }); 81 | 82 | it('isObservable() = true (Dependency)', function () { 83 | expect(blocks.isObservable(blocks.observable(function () { 84 | return 1 + 3; 85 | }))).toBe(true); 86 | }); 87 | 88 | it('isObservable() = true (Array)', function () { 89 | expect(blocks.isObservable(blocks.observable(new Array()))).toBe(true); 90 | expect(blocks.isObservable(blocks.observable([]))).toBe(true); 91 | }); 92 | 93 | it('object is not an observable', function () { 94 | expect(blocks.isObservable({})).toBe(false); 95 | expect(blocks.isObservable(new Object())).toBe(false); 96 | expect(blocks.isObservable({ 97 | __identity__: 'value', 98 | _isDependencyObservable: true 99 | })).toBe(false); 100 | }); 101 | 102 | it('array is not an observable', function () { 103 | expect(blocks.isObservable([])).toBe(false); 104 | expect(blocks.isObservable(new Array())).toBe(false); 105 | }); 106 | 107 | it('date is not an observable', function () { 108 | expect(blocks.isObservable(new Date())).toBe(false); 109 | }); 110 | 111 | it('number is not an observable', function () { 112 | expect(blocks.isObservable(3)).toBe(false); 113 | expect(blocks.isObservable(3.14)).toBe(false); 114 | }); 115 | 116 | it('string is not an observable', function () { 117 | expect(blocks.isObservable('')).toBe(false); 118 | expect(blocks.isObservable(new String())).toBe(false); 119 | expect(blocks.isObservable('__blocks.observable__')).toBe(false); 120 | }); 121 | 122 | it('passing null or undefined returns false', function () { 123 | expect(blocks.isObservable(null)).toBe(false); 124 | expect(blocks.isObservable(undefined)).toBe(false); 125 | }); 126 | 127 | }); 128 | 129 | describe('blocks.dataItem()', function () { 130 | it('returns null when null or undefined is passed', function () { 131 | expect(blocks.dataItem(null)).toBe(null); 132 | expect(blocks.dataItem(undefined)).toBe(null); 133 | }); 134 | 135 | it('returns null when nothing is passed', function () { 136 | expect(blocks.dataItem()).toBe(null); 137 | }); 138 | 139 | it('returns null when empty plain object is passed', function () { 140 | expect(blocks.dataItem({})).toBe(null); 141 | }); 142 | 143 | it('supports passing a jQuery object', function () { 144 | initializeFixtures(); 145 | 146 | $('#testElement').attr('data-query', 'with(context)'); 147 | 148 | $('
    ').appendTo($('#testElement')).append($('
    ')); 149 | 150 | var context = {}; 151 | query({ 152 | context: context 153 | }); 154 | 155 | expect(blocks.dataItem($('.testElement'))).toBe(context); 156 | }); 157 | }); 158 | 159 | describe('blocks.context()', function () { 160 | it('returns null when null or undefined is passed', function () { 161 | expect(blocks.context(null)).toBe(null); 162 | expect(blocks.context(undefined)).toBe(null); 163 | }); 164 | 165 | it('returns null when nothing is passed', function () { 166 | expect(blocks.context()).toBe(null); 167 | }); 168 | 169 | it('returns null when empty plain object is passed', function () { 170 | expect(blocks.context({})).toBe(null); 171 | }); 172 | 173 | it('supports passing a jQuery object', function () { 174 | initializeFixtures(); 175 | 176 | $('#testElement').attr('data-query', 'with(context)'); 177 | 178 | $('
    ').appendTo($('#testElement')).append($('
    ')); 179 | 180 | var context = {}; 181 | query({ 182 | context: context 183 | }); 184 | 185 | expect(blocks.context($('.testElement')).$this).toBe(context); 186 | }); 187 | }); 188 | 189 | describe('blocks.domQuery()', function () { 190 | 191 | }); 192 | })(); -------------------------------------------------------------------------------- /test/spec/query/queries.comments.js: -------------------------------------------------------------------------------- 1 | ; (function () { 2 | testModule('blocks.queries.methodName', function (methodName) { 3 | function isPreprocess() { 4 | return methodName === 'preprocess'; 5 | } 6 | 7 | function initializeFixtures(query, text) { 8 | query = query || ''; 9 | var $fixture = $('
    ', { 10 | id: 'sandbox' 11 | }); 12 | var fixture = $fixture[0]; 13 | fixture.appendChild(document.createComment('blocks ' + query)); 14 | if (text) { 15 | fixture.appendChild(document.createTextNode(text)); 16 | } 17 | fixture.appendChild(document.createComment('/blocks')); 18 | 19 | setFixtures($fixture); 20 | } 21 | 22 | function query(model) { 23 | var queriesCache = {}; 24 | var query; 25 | 26 | if (methodName == 'update') { 27 | for (query in blocks.queries) { 28 | if (blocks.queries[query].update && !(query in { 'each': true, 'with': true, 'render': true })) { 29 | queriesCache[query] = blocks.queries[query].preprocess; 30 | blocks.queries[query].preprocess = null; 31 | } 32 | } 33 | } 34 | 35 | blocks.query(model || {}, document.getElementById('sandbox')); 36 | 37 | if (methodName == 'update') { 38 | for (query in blocks.queries) { 39 | if (queriesCache[query] && !(query in { 'each': true, 'with': true, 'render': true })) { 40 | blocks.queries[query].preprocess = queriesCache[query]; 41 | } 42 | } 43 | } 44 | } 45 | 46 | function expectResult(value) { 47 | expect($('#sandbox')[0].childNodes[1].nodeValue).toBe(value); 48 | } 49 | 50 | describe('blocks.queries.define works on comments', function () { 51 | 52 | }); 53 | 54 | describe('blocks.queries.with works on comments', function () { 55 | 56 | }); 57 | 58 | describe('blocks.queries.render (comments)', function () { 59 | 60 | }); 61 | 62 | describe('blocks.queries.if (comments)', function () { 63 | 64 | }); 65 | 66 | describe('blocks.queries.ifnot (comments)', function () { 67 | 68 | }); 69 | 70 | describe('blocks.quries.visible (comments)', function () { 71 | 72 | }); 73 | }); 74 | })(); -------------------------------------------------------------------------------- /test/tests.json: -------------------------------------------------------------------------------- 1 | { 2 | "blocks.dataSource": [ 3 | "spec/dataSource/api.js", 4 | "spec/dataSource/configuration.js", 5 | "spec/dataSource/events.js" 6 | ], 7 | "blocks.mvc": [ 8 | "spec/mvc/application.js", 9 | "spec/mvc/collection.js", 10 | "spec/mvc/history.js", 11 | "spec/mvc/model.js", 12 | "spec/mvc/property.js", 13 | "spec/mvc/router.js", 14 | "spec/mvc/validation.js", 15 | "spec/mvc/view.js" 16 | ], 17 | "blocks.query": [ 18 | "spec/query/attribute-queries.js", 19 | "spec/query/contexts.js", 20 | "spec/query/custom-queries.js", 21 | "spec/query/element.js", 22 | "spec/query/expressions.js", 23 | "spec/query/extenders.js", 24 | "spec/query/html-parsing.js", 25 | "spec/query/observable.array.js", 26 | "spec/query/observables.comments.js", 27 | "spec/query/observables.dom.js", 28 | "spec/query/observables.js", 29 | "spec/query/public-methods.js", 30 | "spec/query/queries.comments.js", 31 | "spec/query/queries.js" 32 | ] 33 | } --------------------------------------------------------------------------------