├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github ├── CONTRIBUTING.md └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── babel.config.js ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── parse-form │ ├── .gitignore │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── babel.config.js │ ├── package-lock.json │ ├── package.json │ ├── src │ │ └── index.js │ ├── static │ │ └── parse-form-demo.html │ ├── test │ │ ├── index.js │ │ └── spec │ │ │ └── common_spec.js │ └── webpack.config.js ├── router │ ├── .gitignore │ ├── .npmignore │ ├── .travis.yml │ ├── LICENSE │ ├── README.md │ ├── babel.config.js │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── index.js │ │ └── router.js │ ├── static │ │ └── router-demo.html │ ├── test │ │ ├── index.js │ │ └── spec │ │ │ ├── gapped_router_spec.js │ │ │ ├── hash_router_spec.js │ │ │ ├── history_router_spec.js │ │ │ ├── simple_router_spec.js │ │ │ └── summary_spec.js │ └── webpack.config.js └── seemple │ ├── .eslintignore │ ├── README.md │ ├── babel.config.js │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── _core │ │ ├── defineprop.js │ │ ├── defs.js │ │ └── init.js │ ├── _dom │ │ ├── default-dollar.js │ │ ├── index.js │ │ └── mq │ │ │ ├── _data.js │ │ │ ├── _html2nodelist.js │ │ │ ├── _init.js │ │ │ ├── add.js │ │ │ ├── index.js │ │ │ ├── off.js │ │ │ ├── on.js │ │ │ └── parsehtml.js │ ├── _helpers │ │ ├── apply.js │ │ ├── assign.js │ │ ├── checkobjecttype.js │ │ ├── debounce.js │ │ ├── deepfind.js │ │ ├── foreach.js │ │ ├── forown.js │ │ ├── is.js │ │ ├── seempleerror.js │ │ └── toarray.js │ ├── array │ │ ├── _afterinit.js │ │ ├── _cheaprecreate.js │ │ ├── _processrendering │ │ │ ├── checkalreadyrendered.js │ │ │ ├── getalreadyrendered.js │ │ │ ├── index.js │ │ │ ├── processpush.js │ │ │ ├── processrecreate.js │ │ │ ├── processremove.js │ │ │ ├── processrerender.js │ │ │ ├── processsort.js │ │ │ ├── processspliceadd.js │ │ │ ├── processunshift.js │ │ │ └── renderitemnode.js │ │ ├── _prototype.js │ │ ├── _pseudonativemethods │ │ │ ├── concat.js │ │ │ ├── createaddingmethod.js │ │ │ ├── createcopywithin.js │ │ │ ├── createfill.js │ │ │ ├── createpseudonativemethod.js │ │ │ ├── createremovingmethod.js │ │ │ ├── createsortingmethod.js │ │ │ ├── createsplice.js │ │ │ ├── entries.js │ │ │ ├── index.js │ │ │ ├── keys.js │ │ │ └── values.js │ │ ├── _reportmodified.js │ │ ├── _staticmembers.js │ │ ├── _toseemplearray.js │ │ ├── from.js │ │ ├── index.js │ │ ├── iterator.js │ │ ├── mediateitem.js │ │ ├── of.js │ │ ├── orderby │ │ │ ├── _pureorderby.js │ │ │ └── index.js │ │ ├── pull.js │ │ ├── recreate │ │ │ ├── _updateobject.js │ │ │ ├── _updatetracked.js │ │ │ └── index.js │ │ ├── rerender.js │ │ ├── restore.js │ │ └── tojson.js │ ├── binders │ │ ├── index.js │ │ ├── input.js │ │ ├── output.js │ │ ├── progress.js │ │ ├── select.js │ │ └── textarea.js │ ├── bindnode │ │ ├── _bindsinglenode.js │ │ ├── _createbindingswitcher.js │ │ ├── _createnodehandler.js │ │ ├── _createobjecthandler.js │ │ ├── _getnodes.js │ │ ├── _selectnodes.js │ │ └── index.js │ ├── bindoptionalnode.js │ ├── bindsandbox.js │ ├── calc │ │ ├── _addsource.js │ │ ├── _createcalchandler.js │ │ └── index.js │ ├── chain.js │ ├── class.js │ ├── defaultbinders.js │ ├── index.js │ ├── instantiate.js │ ├── lookforbinder.js │ ├── mediate.js │ ├── object │ │ ├── _afterinit.js │ │ ├── _prototype.js │ │ ├── adddatakeys.js │ │ ├── each.js │ │ ├── entries.js │ │ ├── index.js │ │ ├── isdatakey.js │ │ ├── iterator.js │ │ ├── keyof.js │ │ ├── keys.js │ │ ├── removedatakeys.js │ │ ├── setdata.js │ │ ├── tojson.js │ │ └── values.js │ ├── off │ │ ├── _removedomlistener.js │ │ ├── _removelistener.js │ │ ├── _removetreelistener.js │ │ ├── _undelegatelistener.js │ │ └── index.js │ ├── on │ │ ├── _adddomlistener.js │ │ ├── _addlistener.js │ │ ├── _addtreelistener.js │ │ ├── _createdomeventhandler.js │ │ ├── _delegatelistener │ │ │ ├── arrayaddhandler.js │ │ │ ├── arrayremovehandler.js │ │ │ ├── changehandler.js │ │ │ ├── index.js │ │ │ ├── objectremovehandler.js │ │ │ └── objectsethandler.js │ │ ├── _domeventregexp.js │ │ ├── _splitbyspaceregexp.js │ │ └── index.js │ ├── once.js │ ├── ondebounce.js │ ├── parsebindings │ │ ├── _parserdata.js │ │ ├── _processattribute │ │ │ ├── _definehiddencontentproperty.js │ │ │ ├── _getbindingkey.js │ │ │ └── index.js │ │ ├── _processtextnode.js │ │ └── index.js │ ├── parserbrackets.js │ ├── remove.js │ ├── seemple │ │ ├── _afterinit.js │ │ ├── _prototype.js │ │ ├── _staticmembers.js │ │ ├── _universalmethods.js │ │ └── index.js │ ├── select.js │ ├── selectall.js │ ├── set.js │ ├── toseemple.js │ ├── trigger │ │ ├── _triggerdomevent.js │ │ ├── _triggerone.js │ │ ├── _triggeronedomevent.js │ │ └── index.js │ ├── unbindnode │ │ ├── _removebinding.js │ │ └── index.js │ └── usedomlibrary.js │ ├── test │ ├── browser-test │ │ ├── SpecRunner.html │ │ ├── es6-promise.min.js │ │ └── jasmine-2.4.1 │ │ │ ├── boot.js │ │ │ ├── console.js │ │ │ ├── jasmine-html.js │ │ │ ├── jasmine.css │ │ │ ├── jasmine.js │ │ │ └── jasmine_favicon.png │ ├── helpers │ │ ├── createspy.js │ │ ├── deepfind.js │ │ ├── makeobject.js │ │ └── simulateclick.js │ ├── index.js │ ├── node-test │ │ └── jasmine.js │ ├── spec │ │ ├── bindings │ │ │ ├── binders_spec.js │ │ │ ├── bindings_parser_spec.js │ │ │ ├── bindings_spec.js │ │ │ ├── default_binders_spec.js │ │ │ └── existence_binder_spec.js │ │ ├── calc_spec.js │ │ ├── chain_spec.js │ │ ├── class_spec.js │ │ ├── events │ │ │ ├── asterisk_spec.js │ │ │ ├── delegated_spec.js │ │ │ ├── events_change_spec.js │ │ │ ├── events_core_spec.js │ │ │ ├── events_dom_spec.js │ │ │ ├── events_summary_spec.js │ │ │ └── tree_change_spec.js │ │ ├── instantiate_spec.js │ │ ├── mediate_spec.js │ │ ├── mq │ │ │ ├── add_spec.js │ │ │ ├── events_spec.js │ │ │ ├── init_spec.js │ │ │ └── parsehtml_spec.js │ │ ├── remove_spec.js │ │ ├── seemple_array │ │ │ ├── common_spec.js │ │ │ ├── iterator_spec.js │ │ │ ├── mediate_item_spec.js │ │ │ ├── model_spec.js │ │ │ ├── native_methods_spec.js │ │ │ ├── native_modifying_methods_spec.js │ │ │ ├── orderby_spec.js │ │ │ ├── pull_spec.js │ │ │ ├── recreate_spec.js │ │ │ ├── renderer_spec.js │ │ │ ├── static_methods_spec.js │ │ │ └── tojson_spec.js │ │ ├── seemple_object │ │ │ ├── common_spec.js │ │ │ ├── datakeys_spec.js │ │ │ ├── each_spec.js │ │ │ ├── iterator_spec.js │ │ │ └── tojson_spec.js │ │ ├── seemple_spec.js │ │ ├── set_spec.js │ │ ├── toseemple_spec.js │ │ └── usedomlibrary_spec.js │ └── webpack-test.config.js │ ├── tools │ ├── banner-and-footer-webpack-plugin.js │ └── generate-package.js │ └── webpack.config.js └── test └── post-publish ├── README.md ├── package.json └── post-publish.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | !.* 2 | node_modules/ 3 | coverage/ 4 | bundle/ 5 | packages/seemple/test/browser-test/ 6 | packages/parse-form/index.js 7 | npm/ 8 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: 'airbnb-base', 4 | plugins: ['output-todo-comments'], 5 | parser: 'babel-eslint', 6 | rules: { 7 | indent: ['error', 2, { SwitchCase: 1 }], 8 | 'no-var': 'error', 9 | 'no-console': 'error', 10 | 'prefer-rest-params': 0, // arguments work faster 11 | 'no-param-reassign': ['error', { props: false }], 12 | 'no-underscore-dangle': 0, // for some hacks and array methods underscore prefix/suffix is required 13 | 'no-use-before-define': 0, // impossible to follow 14 | 'global-require': 0, // allow to fix circular refs 15 | 'new-cap': ['error', { capIsNewExceptions: ['Class'] }], 16 | 'comma-dangle': ['error', 'never'], // personal preference 17 | 'no-continue': 0, // continue statements are useful to flatten nested blocks 18 | 'import/no-extraneous-dependencies': 0, // that's a bad practice, but modu;es from /packages use modules from projecr_root/node_modules 19 | 'import/no-unresolved': ['error', { ignore: ['^seemple', '^src'] }], // allow to use 'seemple/' and 'src/' as modules in tests and relative packages 20 | 'no-cond-assign': ['error', 'except-parens'], // sometimes it's needed in while() 21 | 'max-lines': ['error', 210], // we may want to decrease this number later 22 | 'no-plusplus': ['error', { allowForLoopAfterthoughts: true }], // x++ is used very often in cycles 23 | 'class-methods-use-this': 0, // it't not required to use this in class methods 24 | 'no-bitwise': ['error', { allow: ['~'] }], // allow to use ~x.indexOf 25 | 'no-restricted-syntax': 0, // for..of is used at tests 26 | 'no-multi-assign': 0, // allow x = y = z 27 | 'prefer-destructuring': 0, // allow things like x = y[z] 28 | 'output-todo-comments/output-todo-comments': [ 29 | 'warn', { 30 | terms: ['todo'], 31 | location: 'start' 32 | } 33 | ], 34 | 'max-classes-per-file': 0, // somt tests use more than 1 class 35 | 'no-mixed-operators': 0 // nah 36 | }, 37 | env: { 38 | jasmine: true 39 | }, 40 | globals: { 41 | window: true 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | There are few important requirements: 4 | 5 | - Commit messages must follow simplified **AngularJS Git Commit Message Conventions**. It's needed for automatic releases using [semantic-release](https://github.com/semantic-release/semantic-release). Simplified means that it's not required to specify a scope and a footer in a commit message. 6 | 7 | Example commit message: 8 | ``` 9 | fix: Make developers happy 10 | ``` 11 | 12 | A body is desirable but not required as well. It can explain **why** did you make such change but should not explain what did you do. 13 | 14 | ``` 15 | fix: Make developers happy 16 | 17 | Happy developers are better than angry developers 18 | ``` 19 | 20 | Don't worry to make a mistake. Git hook throws an error when bad commit message is used. Also you can run ``npm run commit`` instead of ``git commit`` to commit your changes via CLI prompt powered by [commitizen](https://github.com/commitizen/cz-cli). 21 | 22 | - It is required to have one commit per fix/feature/chore etc. 23 | - Fixes (more than just a fix of a typo) and features (any # of lines) must be followed by a test. 24 | - The coverage must not be lower after your commit (Coveralls integration will warn about it). 25 | - New features need to be discussed first (open an issue). 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.log 3 | .directory 4 | coverage 5 | .coveralls.yml 6 | bundle 7 | npm 8 | .npmrc 9 | .DS_Store 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '12' 4 | install: 5 | - npm ci 6 | - npm run install-all 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2013-2016 Andrey Gubanov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation 7 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 8 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions 11 | of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 14 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 15 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 16 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 17 | DEALINGS IN THE SOFTWARE. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # seemple.js [![npm version](https://badge.fury.io/js/seemple.svg)](https://badge.fury.io/js/seemple) [![Coverage Status](https://coveralls.io/repos/github/finom/seemple/badge.svg?branch=master)](https://coveralls.io/github/finom/seemple?branch=master) [![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/) 2 | 3 | 4 | 5 | 6 | [![Seemple Website](https://seemple.js.org/logo/seemple-full-horizontal.svg)](https://seemple.js.org) 7 | 8 | > Seemple.js is a simple JavaScript framework to create single-page applications (SPAs). The simple and intuitive API is based on JavaScript classes and accessors which are accessible to all application developers of varying skills. The requirement to start your first project with Seemple.js is basic knowledge of JavaScript! 9 | 10 | ## Features 11 | - A nice reactive API to solve hard problems 12 | - High robustness of developed apps 13 | - The ability to refactor legacy applications without rewriting them from scratch 14 | - Only couple of hours is needed to master the framework because of the absence of complex concepts 15 | 16 | A bonus: the framework is documented in 3 languages: [English](https://seemple.js.org), [Ukrainian](https://seemple.js.org/ua) and [Russian](https://seemple.js.org/ru). 17 | 18 | **[Download](https://github.com/finom/seemple/tree/gh-pages)** 19 | 20 | **Install via NPM** 21 | 22 | ``` 23 | npm install seemple 24 | ``` 25 | 26 | ## Business needs 27 | Due to extreme simplicity of the framework, even novice web developers can quickly start to do small, then medium and then large web applications. This means that web studios are able to save money by hiring younger professionals, who, in turn, could not find a job before. 28 | 29 | ## Project structure 30 | The project is structured as a monorepository powered by Lerna. */package* folder includes three packages: 31 | 32 | - [seemple](packages/seemple) itself 33 | - [seemple-router](packages/router) for routing 34 | - [seemple-parse-form](packages/parse-form) to make it easy to work with regular HTML forms 35 | 36 | ## Resources 37 | - [Website](https://seemple.js.org) 38 | - [Examples and tutorials](https://github.com/finom/seemple-examples-and-tutorials) 39 | - [JSDoc files](https://github.com/finom/seemple.js.org/tree/master/doc) 40 | 41 | **License:** [MIT License](https://raw.github.com/finom/seemple/master/LICENSE) 42 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@babel/preset-env' 4 | ], 5 | plugins: [ 6 | ['@babel/plugin-transform-runtime'], 7 | '@babel/plugin-proposal-class-properties' 8 | ] 9 | }; 10 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "2.4.18" 6 | } 7 | -------------------------------------------------------------------------------- /packages/parse-form/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.log 3 | .directory 4 | coverage 5 | .coveralls.yml 6 | npm 7 | bundle 8 | /npm 9 | -------------------------------------------------------------------------------- /packages/parse-form/.npmignore: -------------------------------------------------------------------------------- 1 | bundle 2 | coverage 3 | test 4 | .eslintrc.json 5 | .gitignore 6 | .travis.yml 7 | src.js 8 | webpack.config.js 9 | .coveralls.yml 10 | .babelrc 11 | -------------------------------------------------------------------------------- /packages/parse-form/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Seemple.js 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/parse-form/README.md: -------------------------------------------------------------------------------- 1 | # seemple-parse-form [![npm version](https://badge.fury.io/js/seemple-parse-form.svg)](https://badge.fury.io/js/seemple-parse-form) 2 | 3 | The function binds named HTML form fields (input, select, textarea, etc.) in a given HTML form to their corresponding properties. 4 | 5 | ```html 6 |
7 | 8 |
9 | ``` 10 | 11 | ```js 12 | const object = {}; 13 | parseForm(object, '.my-form'); 14 | 15 | // ... 16 | console.log(object.x); // 'foo' 17 | object.x = 'bar'; // changes input value to 'bar' 18 | ``` 19 | 20 | ## Usage 21 | 22 | In **browser environment** (or whatever environment where ``Seemple`` is global variable) ``Seemple`` is extended. 23 | ```html 24 | 25 | ``` 26 | 27 | ```js 28 | Seemple.parseForm(object, form); 29 | ``` 30 | 31 | The bundle can be downloaded at [gh-pages branch](https://github.com/finom/seemple/tree/gh-pages) 32 | 33 | ------------- 34 | 35 | In **CJS environment** ``Seemple`` is not extended. 36 | 37 | ``` 38 | npm install seemple-parse-form 39 | ``` 40 | 41 | ```js 42 | const parseForm = require('seemple-parse-form'); 43 | ``` 44 | 45 | 46 | ## API 47 | 48 | The function accepts 4 arguments: 49 | - ``object`` - an object (required) 50 | - ``form`` - a selector, DOM node, etc. of the given form (custom selectors ``:sandbox`` and ``:bound(XXX)`` also acceptable) (required) 51 | - ``callback`` - a function which will be called on every found field; accepts field name and field element itself 52 | - ``eventOptions`` - event options which will be passed to every internal call of ``bindNode``. 53 | 54 | Returns: parsed form element. 55 | 56 | 57 | The third argument is useful when ``parseForm`` is used with ``Seemple.Object``: you can call ``addDataKeys`` method there. 58 | ```js 59 | const form = parseForm(this, ':sandbox .foo', key => this.addDataKeys(key), { 60 | getValueOnBind: false 61 | }); 62 | ``` 63 | -------------------------------------------------------------------------------- /packages/parse-form/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/preset-env'] 3 | }; 4 | -------------------------------------------------------------------------------- /packages/parse-form/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seemple-parse-form", 3 | "version": "2.4.17", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "balajs": { 8 | "version": "1.0.7", 9 | "resolved": "https://registry.npmjs.org/balajs/-/balajs-1.0.7.tgz", 10 | "integrity": "sha512-ef6Gb4Nb44Y3fh1NHj1U9cjnkrj5pcz/16USEziNX+qZd0SEoXegU2IEl2DEcBUAKZnL9hWPT2yOPlBPAlCRSA==" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/parse-form/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seemple-parse-form", 3 | "version": "2.4.17", 4 | "description": "Binds named fields of HTML5 form", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "npm run cover", 8 | "cover": "../../node_modules/.bin/babel-node ../../node_modules/.bin/babel-istanbul cover test/index.js", 9 | "npm-compile": "babel src -d npm && cp package.json npm/package.json && cp README.md npm/README.md", 10 | "build": "../../node_modules/.bin/webpack --mode=production" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/finom/seemple-parse-form.git" 15 | }, 16 | "keywords": [ 17 | "seemplejs", 18 | "codemirror" 19 | ], 20 | "author": "Andrey Gubanov", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/finom/seemple-parse-form/issues" 24 | }, 25 | "homepage": "https://github.com/finom/seemple-parse-form#readme", 26 | "peerDependencies": { 27 | "seemple": "2.x" 28 | }, 29 | "dependencies": { 30 | "balajs": "^1.0.7" 31 | }, 32 | "gitHead": "73cc162ade078967f510f052ee94a782a807a8af" 33 | } 34 | -------------------------------------------------------------------------------- /packages/parse-form/src/index.js: -------------------------------------------------------------------------------- 1 | /* globals Seemple */ 2 | const $ = require('balajs'); 3 | const { select, bindNode } = require('seemple'); 4 | 5 | function parseForm(object, selector, callback, eventOptions) { 6 | const form = /:sandbox|:bound/.test(selector) ? select(object, selector) : $.one(selector); 7 | const fields = $('input, textarea, output, progress, select', form); 8 | 9 | /* istanbul ignore if */ 10 | if (!object || typeof object !== 'object' && typeof object !== 'function') { 11 | throw new TypeError('parseForm should accept an object or a function as the first argument'); 12 | } 13 | 14 | for (let i = 0; i < fields.length; i++) { 15 | const field = fields[i]; 16 | const { name } = field; 17 | 18 | if (name) { 19 | bindNode(object, name, field, undefined, eventOptions); 20 | if (callback) { 21 | callback(name, field); 22 | } 23 | } 24 | } 25 | 26 | return form; 27 | } 28 | 29 | // extend Seemple in browser environment 30 | /* istanbul ignore if */ 31 | if (typeof Seemple === 'function') { 32 | Seemple.parseForm = parseForm; 33 | } 34 | 35 | module.exports = parseForm; 36 | -------------------------------------------------------------------------------- /packages/parse-form/static/parse-form-demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 |
13 | 14 | 15 | 16 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /packages/parse-form/test/index.js: -------------------------------------------------------------------------------- 1 | const Jasmine = require('jasmine'); 2 | const { JSDOM } = require('jsdom'); 3 | const { addAlias } = require('module-alias'); 4 | const path = require('path'); 5 | 6 | const jasmine = new Jasmine(); 7 | 8 | global.window = new JSDOM('', { 9 | url: 'http://localhost' 10 | }).window; 11 | 12 | global.document = global.window.document; 13 | 14 | jasmine.loadConfig({ 15 | spec_dir: 'test/spec', 16 | spec_files: [ 17 | '**/**_spec.js' 18 | ] 19 | }); 20 | 21 | addAlias('seemple', path.resolve(__dirname, '../../seemple/npm')); 22 | 23 | jasmine.execute(); 24 | -------------------------------------------------------------------------------- /packages/parse-form/test/spec/common_spec.js: -------------------------------------------------------------------------------- 1 | import parseForm from '../../src'; 2 | 3 | const noDebounceFlag = { debounceGetValue: false, debounceSetValue: false }; 4 | 5 | describe('Common', () => { 6 | it('runs all the stuff', () => { 7 | const spyObject = { 8 | spy(key, node) { 9 | expect(key).toEqual('x'); 10 | expect(node.name).toEqual('x'); 11 | expect(node.type).toEqual('INPUT'); 12 | } 13 | }; 14 | 15 | const object = {}; 16 | 17 | spyOn(spyObject, 'spy'); 18 | 19 | const form = parseForm(object, `
20 | 21 |
`, spyObject.spy, noDebounceFlag); 22 | 23 | object.x = 'foo'; 24 | 25 | expect(form.querySelector('[name="x"]').value).toEqual('foo'); 26 | expect(spyObject.spy).toHaveBeenCalledTimes(1); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/parse-form/webpack.config.js: -------------------------------------------------------------------------------- 1 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | devtool: 'source-map', 6 | entry: './src', 7 | output: { 8 | path: path.resolve(__dirname, '../../bundle'), 9 | filename: 'seemple-parse-form.min.js', 10 | libraryTarget: 'umd' 11 | }, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.js$/, 16 | use: ['babel-loader'] 17 | } 18 | ] 19 | }, 20 | externals: { 21 | seemple: { 22 | commonjs: 'seemple', 23 | commonjs2: 'seemple', 24 | amd: 'seemple', 25 | root: 'Seemple' 26 | } 27 | }, 28 | plugins: [ 29 | new CopyWebpackPlugin([ 30 | { from: 'static', to: '.' } 31 | ]) 32 | ] 33 | }; 34 | -------------------------------------------------------------------------------- /packages/router/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.log 3 | .directory 4 | coverage 5 | .coveralls.yml 6 | npm 7 | bundle 8 | /npm 9 | -------------------------------------------------------------------------------- /packages/router/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | test 3 | .babelrc 4 | .coveralls.yml 5 | .directory 6 | .gitignore 7 | .travis.yml 8 | webpack.config.js 9 | -------------------------------------------------------------------------------- /packages/router/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | directories: 4 | - node_modules 5 | node_js: 6 | - '6' 7 | before_script: 8 | - npm prune 9 | branches: 10 | except: 11 | - /^v\d+\.\d+\.\d+$/ 12 | - gh-pages 13 | after_success: 14 | if ([ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ]); then 15 | eval 'npm run semantic-release'; 16 | fi 17 | -------------------------------------------------------------------------------- /packages/router/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Andrey Gubanov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/router/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@babel/preset-env' 4 | ], 5 | plugins: [ 6 | '@babel/plugin-proposal-class-properties' 7 | ] 8 | }; 9 | -------------------------------------------------------------------------------- /packages/router/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seemple-router", 3 | "version": "2.4.18", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /packages/router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seemple-router", 3 | "version": "2.4.18", 4 | "description": "A router for Seemple.js", 5 | "main": "index", 6 | "scripts": { 7 | "test": "npm run unit", 8 | "unit": "../../node_modules/.bin/babel-node ../../node_modules/.bin/babel-istanbul cover test/index.js", 9 | "lint": "eslint ./test ./src", 10 | "npm-compile": "babel src -d npm && cp package.json npm/package.json && cp README.md npm/README.md", 11 | "build": "../../node_modules/.bin/webpack --mode=production" 12 | }, 13 | "config": { 14 | "commitizen": { 15 | "path": "cz-simple-conventional-changelog" 16 | }, 17 | "validate-commit-msg": { 18 | "types": [ 19 | "feat", 20 | "fix", 21 | "refactor", 22 | "perf", 23 | "test", 24 | "chore", 25 | "revert" 26 | ] 27 | }, 28 | "ghooks": { 29 | "commit-msg": "validate-commit-msg" 30 | } 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "https://github.com/finom/seemple-router.git" 35 | }, 36 | "keywords": [ 37 | "seemple", 38 | "router" 39 | ], 40 | "author": "Andrey Gubanov", 41 | "license": "MIT", 42 | "bugs": { 43 | "url": "https://github.com/finom/seemple-router/issues" 44 | }, 45 | "homepage": "https://github.com/finom/seemple-router#readme", 46 | "peerDependencies": { 47 | "seemple": "2.x" 48 | }, 49 | "gitHead": "73cc162ade078967f510f052ee94a782a807a8af" 50 | } 51 | -------------------------------------------------------------------------------- /packages/router/src/index.js: -------------------------------------------------------------------------------- 1 | /* globals Seemple */ 2 | const Router = require('./router'); 3 | 4 | function initRouter(obj, route, type) { 5 | Router[type || 'hash'].subscribe(obj, route); 6 | return obj; 7 | } 8 | 9 | /* istanbul ignore if */ 10 | if (typeof Seemple === 'function') { 11 | Seemple.Router = Router; 12 | Seemple.initRouter = initRouter; 13 | } 14 | 15 | 16 | module.exports = initRouter; 17 | -------------------------------------------------------------------------------- /packages/router/static/router-demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | JS Bin 7 | 8 | 9 | 10 |
11 |
12 | 16 | 21 | 25 |
26 |
27 | 31 | 36 | 40 |
41 |
42 | 43 | 44 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /packages/router/test/index.js: -------------------------------------------------------------------------------- 1 | const Jasmine = require('jasmine'); 2 | const { JSDOM } = require('jsdom'); 3 | const { SpecReporter } = require('jasmine-spec-reporter'); 4 | const { addAlias } = require('module-alias'); 5 | const path = require('path'); 6 | 7 | const jasmine = new Jasmine(); 8 | 9 | global.window = new JSDOM('', { 10 | url: 'http://localhost' 11 | }).window; 12 | 13 | jasmine.loadConfig({ 14 | random: false, 15 | spec_dir: 'test/spec', 16 | spec_files: [ 17 | '**/*_spec.js' 18 | ] 19 | }); 20 | 21 | addAlias('seemple', path.resolve(__dirname, '../../seemple/npm')); 22 | 23 | jasmine.addReporter(new SpecReporter()); 24 | 25 | jasmine.execute(); 26 | -------------------------------------------------------------------------------- /packages/router/test/spec/gapped_router_spec.js: -------------------------------------------------------------------------------- 1 | import Router from '../../src/router'; 2 | 3 | describe('Gapped router (API test)', () => { 4 | const obj = { 5 | a: 'foo', 6 | b: 'bar', 7 | c: 'baz' 8 | }; 9 | const router = new Router(null).subscribe(obj, 'a/*/c/*/e/f'); 10 | 11 | it('initializes correctly', () => { 12 | expect(obj.a).toEqual('foo'); 13 | expect(obj.b).toEqual('bar'); 14 | expect(obj.c).toEqual(null); // because 2nd part is not set 15 | expect(obj.d).toEqual(undefined); 16 | expect(obj.e).toEqual(null); 17 | expect(obj.f).toEqual(null); 18 | }); 19 | 20 | it('changes properties when URL is changed', () => { 21 | router.path = '/bar/baz/qux/eggs/bat/lol/'; 22 | 23 | expect(obj.a).toEqual('bar'); 24 | expect(obj.b).toEqual('bar'); 25 | expect(obj.c).toEqual('qux'); 26 | expect(obj.d).toEqual(undefined); 27 | expect(obj.e).toEqual('bat'); 28 | expect(obj.f).toEqual('lol'); 29 | }); 30 | 31 | it('changes URL when property is changed', () => { 32 | obj.c = 'poo'; 33 | expect(router.path).toEqual('/bar/baz/poo/eggs/bat/lol/'); 34 | expect(router.hashPath).toEqual('#!/bar/baz/poo/eggs/bat/lol/'); 35 | }); 36 | 37 | it('sets further parts as null if one of parts is null', () => { 38 | obj.c = null; 39 | 40 | expect(obj.a).toEqual('bar'); 41 | expect(obj.b).toEqual('bar'); 42 | expect(obj.c).toEqual(null); 43 | expect(obj.d).toEqual(undefined); 44 | expect(obj.e).toEqual(null); 45 | expect(obj.f).toEqual(null); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/router/test/spec/hash_router_spec.js: -------------------------------------------------------------------------------- 1 | import Router from '../../src/router'; 2 | 3 | const { document } = window; 4 | 5 | describe('Hash routing', () => { 6 | const obj = { a: 'foo' }; 7 | new Router('hash').subscribe(obj, 'a/b/c/d'); 8 | 9 | it('initializes correctly', (done) => { 10 | expect(obj.a).toEqual('foo'); 11 | expect(obj.b).toEqual(null); 12 | expect(obj.c).toEqual(null); 13 | expect(obj.d).toEqual(null); 14 | 15 | setTimeout(() => { 16 | expect(document.location.hash).toEqual('#!/foo/'); 17 | done(); 18 | }, 50); 19 | }); 20 | 21 | it('changes properties when URL (hash) is changed', (done) => { 22 | document.location.hash = '#!/bar/baz/qux/'; 23 | 24 | setTimeout(() => { 25 | expect(obj.a).toEqual('bar'); 26 | expect(obj.b).toEqual('baz'); 27 | expect(obj.c).toEqual('qux'); 28 | expect(obj.d).toEqual(null); 29 | done(); 30 | }, 50); 31 | }); 32 | 33 | it('changes URL (hash) when property is changed', (done) => { 34 | obj.b = 'lol'; 35 | setTimeout(() => { 36 | expect(document.location.hash).toEqual('#!/bar/lol/qux/'); 37 | done(); 38 | }, 50); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/router/test/spec/history_router_spec.js: -------------------------------------------------------------------------------- 1 | import Router from '../../src/router'; 2 | 3 | const { document } = window; 4 | 5 | describe('HTML5 History routing', () => { 6 | const obj = { a: 'foo' }; 7 | let router; 8 | 9 | beforeAll(() => { 10 | router = new Router('history').subscribe(obj, 'a/b/c/d'); 11 | }); 12 | 13 | it('initializes correctly', (done) => { 14 | expect(obj.a).toEqual('foo'); 15 | expect(obj.b).toEqual(null); 16 | expect(obj.c).toEqual(null); 17 | expect(obj.d).toEqual(null); 18 | 19 | setTimeout(() => { 20 | expect(document.location.pathname).toEqual('/foo/'); 21 | done(); 22 | }, 50); 23 | }); 24 | 25 | it('changes properties when URL (pathname) is changed', (done) => { 26 | router.path = '/bar/baz/qux/'; 27 | 28 | setTimeout(() => { 29 | expect(obj.a).toEqual('bar'); 30 | expect(obj.b).toEqual('baz'); 31 | expect(obj.c).toEqual('qux'); 32 | expect(obj.d).toEqual(null); 33 | done(); 34 | }, 50); 35 | }); 36 | 37 | it('changes URL (pathname) when property is changed', (done) => { 38 | obj.b = 'lol'; 39 | setTimeout(() => { 40 | expect(document.location.pathname).toEqual('/bar/lol/qux/'); 41 | done(); 42 | }, 50); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/router/test/spec/simple_router_spec.js: -------------------------------------------------------------------------------- 1 | import Router from '../../src/router'; 2 | 3 | describe('Simple router (API test)', () => { 4 | const obj = { a: 'foo' }; 5 | const router = new Router(null).subscribe(obj, 'a/b/c/d'); 6 | 7 | 8 | it('initializes correctly', () => { 9 | expect(obj.a).toEqual('foo'); 10 | expect(obj.b).toEqual(null); 11 | expect(obj.c).toEqual(null); 12 | expect(obj.d).toEqual(null); 13 | }); 14 | 15 | it('changes properties when URL is changed', () => { 16 | router.path = '/bar/baz/qux/'; 17 | 18 | expect(obj.a).toEqual('bar'); 19 | expect(obj.b).toEqual('baz'); 20 | expect(obj.c).toEqual('qux'); 21 | expect(obj.d).toEqual(null); 22 | }); 23 | 24 | it('changes URL when property is changed', () => { 25 | obj.b = 'lol'; 26 | expect(router.path).toEqual('/bar/lol/qux/'); 27 | expect(router.hashPath).toEqual('#!/bar/lol/qux/'); 28 | }); 29 | 30 | it('sets further parts as null if one of parts is null', () => { 31 | obj.b = null; 32 | 33 | expect(obj.a).toEqual('bar'); 34 | expect(obj.b).toEqual(null); 35 | expect(obj.c).toEqual(null); 36 | expect(obj.d).toEqual(null); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/router/webpack.config.js: -------------------------------------------------------------------------------- 1 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 2 | 3 | const path = require('path'); 4 | 5 | module.exports = { 6 | devtool: 'source-map', 7 | entry: './src/index', 8 | output: { 9 | path: path.resolve(__dirname, '../../bundle'), 10 | filename: 'seemple-router.min.js', 11 | libraryTarget: 'umd' 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.js$/, 17 | // exclude: /node_modules/, 18 | use: ['babel-loader'] 19 | } 20 | ] 21 | }, 22 | 23 | externals: { 24 | seemple: { 25 | commonjs: 'seemple', 26 | commonjs2: 'seemple', 27 | amd: 'seemple', 28 | root: 'Seemple' 29 | } 30 | }, 31 | plugins: [ 32 | new CopyWebpackPlugin([ 33 | { from: 'static', to: '.' } 34 | ]) 35 | ] 36 | }; 37 | -------------------------------------------------------------------------------- /packages/seemple/.eslintignore: -------------------------------------------------------------------------------- 1 | bundle/* 2 | npm/* 3 | coverage/* 4 | test/browser-test/* 5 | test/coverage/* 6 | test/karma-test/vendor-dom-libraries/* 7 | -------------------------------------------------------------------------------- /packages/seemple/README.md: -------------------------------------------------------------------------------- 1 | # seemple.js 2 | 3 | This is a README placeholder. An actual README which is also going to be published at NPM is placed [in the root of the project](../../README.md). 4 | -------------------------------------------------------------------------------- /packages/seemple/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@babel/preset-env' 4 | ], 5 | plugins: [ 6 | ['@babel/plugin-transform-runtime'], 7 | '@babel/plugin-proposal-class-properties' 8 | ] 9 | }; 10 | -------------------------------------------------------------------------------- /packages/seemple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seemple", 3 | "version": "2.4.18", 4 | "description": "Seemple.js framework", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "npm run node-cover && npm run check-coverage", 11 | "node-test": "BABEL_ENV=test ../../node_modules/.bin/babel-node test/node-test/jasmine.js", 12 | "node-cover": "BABEL_ENV=test ../../node_modules/.bin/babel-node ../../node_modules/.bin/babel-istanbul cover test/node-test/jasmine.js", 13 | "check-coverage": "babel-istanbul check-coverage --lines 95", 14 | "watch": "webpack --config ./webpack.config.js --watch", 15 | "watch-browser-test": "../../node_modules/.bin/webpack --config test/webpack-test.config.js --watch", 16 | "deploy": "npm run deploy-to-git && npm run npm-deploy", 17 | "npm-compile": "shx rm -rf npm && ../../node_modules/.bin/babel src -d npm --source-maps && shx cp ../../README.md npm/README.md && node ./tools/generate-package", 18 | "npm-publish": "cd npm && npm publish && cd ..", 19 | "npm-deploy": "npm run npm-compile && npm run npm-publish", 20 | "build-seemple": "../../node_modules/.bin/webpack --config ./webpack.config.js --mode production", 21 | "build-browser-test": "BABEL_ENV=test ../../node_modules/.bin/webpack --config test/webpack-test.config.js --mode development", 22 | "build": "npm run build-seemple && npm run build-browser-test" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/finom/seemple.git" 27 | }, 28 | "keywords": [ 29 | "seemple" 30 | ], 31 | "author": "Andrey Gubanov", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/finom/seemple/issues" 35 | }, 36 | "homepage": "https://github.com/finom/seemple#readme", 37 | "dependencies": { 38 | "common-binders": "0.1.2" 39 | }, 40 | "devDependencies": { 41 | "shx": "^0.3.2" 42 | }, 43 | "gitHead": "73cc162ade078967f510f052ee94a782a807a8af" 44 | } 45 | -------------------------------------------------------------------------------- /packages/seemple/src/_core/defineprop.js: -------------------------------------------------------------------------------- 1 | import defs from './defs'; 2 | import set from '../set'; 3 | import seempleError from '../_helpers/seempleerror'; 4 | 5 | function errorAccessor() { 6 | throw seempleError('common:use_magic_props'); 7 | } 8 | 9 | // the function defines needed descriptor for given property 10 | export default function defineProp(object, key, noAccessor) { 11 | const def = defs.get(object); 12 | 13 | // if no object definition do nothing 14 | if (!def) { 15 | return null; 16 | } 17 | 18 | if (!def.props[key]) { 19 | const propDef = def.props[key] = { 20 | value: object[key], 21 | mediator: null, 22 | bindings: null 23 | }; 24 | let getter; 25 | let setter; 26 | 27 | // make possible to throw an error on get and on set if sandbox (for all objects) 28 | // or container (for Seemple.Array instances) are used 29 | if (key === 'sandbox' || (object.isSeempleArray && key === 'container')) { 30 | getter = setter = errorAccessor; 31 | } 32 | 33 | if (!noAccessor) { 34 | Object.defineProperty(object, key, { 35 | configurable: true, 36 | enumerable: true, 37 | get() { 38 | return getter ? getter() : propDef.value; 39 | }, 40 | set(v) { 41 | return setter ? setter() : set(object, key, v, { 42 | fromSetter: true 43 | }); 44 | } 45 | }); 46 | } 47 | } 48 | 49 | return def.props[key]; 50 | } 51 | -------------------------------------------------------------------------------- /packages/seemple/src/_core/defs.js: -------------------------------------------------------------------------------- 1 | import assign from '../_helpers/assign'; 2 | 3 | function PseudoMap() {} 4 | 5 | // PseudoMap simulates WeakMap behavior with O(1) search complexity 6 | // it's needed to support @IE9 and @IE10 7 | assign(PseudoMap.prototype, { 8 | get(obj) { 9 | return obj.seempleData; 10 | }, 11 | set(obj, data) { 12 | Object.defineProperty(obj, 'seempleData', { 13 | value: data, 14 | enumerable: false, 15 | writable: false, 16 | configurable: false 17 | }); 18 | }, 19 | has(obj) { 20 | return 'seempleData' in obj; 21 | } 22 | }); 23 | 24 | export default typeof WeakMap === 'undefined' ? new PseudoMap() : new WeakMap(); 25 | -------------------------------------------------------------------------------- /packages/seemple/src/_core/init.js: -------------------------------------------------------------------------------- 1 | import defs from './defs'; 2 | 3 | let objectId = 0; 4 | 5 | // this is common function which associates an object with its Seemple definition 6 | export default function initSeemple(object) { 7 | let def = defs.get(object); 8 | if (!def) { 9 | def = { 10 | // a property name of "events" object is an event name 11 | // and a value is an array of event handlers 12 | events: { 13 | /* example: { 14 | callback: function, 15 | ctx: object, 16 | context: object2, 17 | name: "example", 18 | info: { ...extra data for an event... } 19 | } */ 20 | }, 21 | // "props" contains special information about properties (getters, setters etc) 22 | props: { 23 | /* example: { 24 | value: object[key], 25 | mediator: null, 26 | bindings: [{ 27 | node, 28 | binder, 29 | nodeHandler, 30 | objectHandler, 31 | ...other required info 32 | }] 33 | } */ 34 | }, 35 | id: objectId 36 | }; 37 | 38 | objectId += 1; 39 | 40 | defs.set(object, def); 41 | 42 | if (object._afterInit) { 43 | object._afterInit(def); 44 | } 45 | } 46 | 47 | return def; 48 | } 49 | -------------------------------------------------------------------------------- /packages/seemple/src/_dom/default-dollar.js: -------------------------------------------------------------------------------- 1 | /* global $ */ 2 | import mq from './mq'; 3 | 4 | // check existence of needed methods in $ global variable 5 | // to use it for internal needs 6 | 7 | const neededMethods = ['on', 'off', 'add']; 8 | 9 | const globalDollar = typeof $ === 'function' ? $ : null; 10 | let useGlobalDollar = true; 11 | 12 | /* istanbul ignore if */ 13 | if (globalDollar) { 14 | const fn = globalDollar.fn || globalDollar.prototype; 15 | for (let i = 0; i < neededMethods.length; i++) { 16 | if (!fn[neededMethods[i]]) { 17 | useGlobalDollar = false; 18 | break; 19 | } 20 | } 21 | 22 | if (!globalDollar.parseHTML) { 23 | // Zepto doesn't include its own parseHTML 24 | // TODO: Assignment of parseHTML is side effect 25 | globalDollar.parseHTML = mq.parseHTML; 26 | } 27 | } else { 28 | useGlobalDollar = false; 29 | } 30 | 31 | export default useGlobalDollar ? globalDollar : mq; 32 | -------------------------------------------------------------------------------- /packages/seemple/src/_dom/index.js: -------------------------------------------------------------------------------- 1 | import $ from './default-dollar'; 2 | 3 | export default { $ }; 4 | -------------------------------------------------------------------------------- /packages/seemple/src/_dom/mq/_data.js: -------------------------------------------------------------------------------- 1 | // an object allows to share data between modules; it's needed because we use 2 | // simplified ES modules there and cannot import and share a number 3 | export default { 4 | nodeIndex: 0, 5 | allEvents: {} 6 | }; 7 | -------------------------------------------------------------------------------- /packages/seemple/src/_dom/mq/_html2nodelist.js: -------------------------------------------------------------------------------- 1 | // converts HTML string to NodeList instance 2 | export default function html2nodeList(givenHTML) { 3 | // wrapMap is taken from jQuery 4 | const wrapMap = { 5 | option: [1, ''], 6 | legend: [1, '
', '
'], 7 | thead: [1, '', '
'], 8 | tr: [2, '', '
'], 9 | td: [3, '', '
'], 10 | col: [2, '', '
'], 11 | area: [1, '', ''], 12 | _: [0, '', ''] 13 | }; 14 | 15 | const html = givenHTML.replace(/^\s+|\s+$/g, ''); 16 | let node = window.document.createElement('div'); 17 | let i; 18 | 19 | wrapMap.optgroup = wrapMap.option; 20 | wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 21 | wrapMap.th = wrapMap.td; 22 | 23 | const ex = /<([\w:]+)/.exec(html); 24 | const wrapper = (ex && wrapMap[ex[1]]) || wrapMap._; 25 | 26 | node.innerHTML = wrapper[1] + html + wrapper[2]; 27 | 28 | i = wrapper[0]; 29 | 30 | while (i) { 31 | i -= 1; 32 | node = node.children[0]; 33 | } 34 | 35 | return node.childNodes; 36 | } 37 | -------------------------------------------------------------------------------- /packages/seemple/src/_dom/mq/_init.js: -------------------------------------------------------------------------------- 1 | import html2nodeList from './_html2nodelist'; 2 | 3 | const win = window; 4 | 5 | // function-constructor of mq library 6 | // accepts many kinds of arguments (selector, html, function) 7 | function MQInit(selector, context) { 8 | let result; 9 | 10 | if (selector) { 11 | if (selector.nodeType || (typeof win === 'object' && selector === win)) { 12 | result = [selector]; 13 | } else if (typeof selector === 'string') { 14 | if (/ { 17 | output[nextKey] = nextValue; 18 | }); 19 | } 20 | } 21 | 22 | return output; 23 | }; 24 | 25 | export default assign; 26 | -------------------------------------------------------------------------------- /packages/seemple/src/_helpers/checkobjecttype.js: -------------------------------------------------------------------------------- 1 | import seempleError from './seempleerror'; 2 | 3 | // checks type of a variable and throws an error if its type is not an object 4 | export default function checkObjectType(object, method) { 5 | const typeofObject = object === null ? 'null' : typeof object; 6 | 7 | if (typeofObject !== 'object' && typeofObject !== 'function') { 8 | throw seempleError('common:object_type', { 9 | object, 10 | method 11 | }); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/seemple/src/_helpers/debounce.js: -------------------------------------------------------------------------------- 1 | import apply from './apply'; 2 | 3 | // Returns a function, that, as long as it continues to be invoked, will not 4 | // be triggered. The function will be called after it stops being called for 5 | // N milliseconds. 6 | // (c) https://davidwalsh.name/javascript-debounce-function 7 | 8 | export default function debounce(func, givenDelay, thisArg) { 9 | let timeout; 10 | let delay; 11 | if (typeof givenDelay !== 'number') { 12 | thisArg = givenDelay; // eslint-disable-line no-param-reassign 13 | delay = 0; 14 | } else { 15 | delay = givenDelay || 0; 16 | } 17 | 18 | return function debounced() { 19 | const args = arguments; 20 | const callContext = thisArg || this; 21 | 22 | clearTimeout(timeout); 23 | 24 | timeout = setTimeout(() => apply(func, callContext, args), delay); 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /packages/seemple/src/_helpers/deepfind.js: -------------------------------------------------------------------------------- 1 | // gets value of a property in nested object 2 | // eg "d" from a.b.c.d 3 | export default function deepFind(obj, givenPath) { 4 | const paths = typeof givenPath === 'string' ? givenPath.split('.') : givenPath; 5 | let current = obj; 6 | 7 | for (let i = 0; i < paths.length; ++i) { 8 | if (typeof current[paths[i]] === 'undefined') { 9 | return undefined; 10 | } 11 | 12 | current = current[paths[i]]; 13 | } 14 | 15 | return current; 16 | } 17 | -------------------------------------------------------------------------------- /packages/seemple/src/_helpers/foreach.js: -------------------------------------------------------------------------------- 1 | export default function forEach(arr, callback) { 2 | let i = 0; 3 | const l = arr.length; 4 | 5 | for (; i < l; i++) { 6 | callback(arr[i], i); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/seemple/src/_helpers/forown.js: -------------------------------------------------------------------------------- 1 | export default function forOwn(obj, callback) { 2 | const keys = Object.keys(obj); 3 | const l = keys.length; 4 | let i = 0; 5 | let key; 6 | 7 | while (i < l) { 8 | key = keys[i]; 9 | i += 1; 10 | callback(obj[key], key); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/seemple/src/_helpers/is.js: -------------------------------------------------------------------------------- 1 | // determines whether two values are the same value 2 | /* istanbul ignore next */ 3 | // eslint-disable-next-line 4 | const isPolyfill = (v1, v2) => v1 === 0 && v2 === 0 ? 1 / v1 === 1 / v2 : v1 !== v1 && v2 !== v2 || v1 === v2; 5 | 6 | export default Object.is || isPolyfill; 7 | -------------------------------------------------------------------------------- /packages/seemple/src/_helpers/toarray.js: -------------------------------------------------------------------------------- 1 | // cheap conversion of an array-like object to Array instance 2 | export default function toArray(object, start = 0) { 3 | const { length } = object; 4 | const array = Array(length); 5 | 6 | for (let i = start; i < length; i++) { 7 | array[i - start] = object[i]; 8 | } 9 | 10 | return array; 11 | } 12 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_afterinit.js: -------------------------------------------------------------------------------- 1 | import afterSeempleInit from '../seemple/_afterinit'; 2 | import addListener from '../on/_addlistener'; 3 | import seempleError from '../_helpers/seempleerror'; 4 | 5 | // the function returns array item converted to Model instance 6 | function modelItemMediator(item, index) { 7 | const { Model } = this; 8 | 9 | // if an item is already instance of Model 10 | if (item instanceof Model) { 11 | return item; 12 | } 13 | 14 | let itemData; 15 | 16 | if (item && typeof item.toJSON === 'function') { 17 | // if item is not falsy and if it has toJSON method 18 | // then retrieve instance data by this method 19 | itemData = item.toJSON(false); 20 | } else { 21 | // if not then use an item as its data 22 | itemData = item; 23 | } 24 | 25 | return new Model(itemData, this, index); 26 | } 27 | 28 | // event handler to listen changes of Model property 29 | function changeModel() { 30 | const { Model } = this; 31 | 32 | // if model has wrong type then throw an error 33 | if (typeof Model !== 'function') { 34 | throw seempleError('array:model_type', { Model }); 35 | } 36 | 37 | // attatch item mediator 38 | this.mediateItem(modelItemMediator); 39 | } 40 | 41 | // event handler to listen changes of itemRenderer property 42 | function changeItemRendererHandler(eventOptions = {}) { 43 | const { forceRerender = true } = eventOptions; 44 | this.rerender({ forceRerender }); 45 | } 46 | 47 | // Seemple.Array initializer 48 | export default function afterSeempleArrayInit() { 49 | // we need to calculate hasModel before change:Model is added 50 | const hasModel = 'Model' in this; 51 | 52 | // call Seemple initializer 53 | afterSeempleInit.call(this); 54 | 55 | addListener(this, '_change:common:Model', changeModel, this, { 56 | skipChecks: true 57 | }); 58 | 59 | addListener(this, '_change:common:itemRenderer', changeItemRendererHandler, this, { 60 | skipChecks: true 61 | }); 62 | 63 | // call changeModel handler immediately if model is present 64 | // it will throw an error if Model is not a function 65 | if (hasModel) { 66 | changeModel.call(this); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_cheaprecreate.js: -------------------------------------------------------------------------------- 1 | // makes cheap array recreation (with no trackBy, with no events, with no item mediator etc) 2 | export default function cheapRecreate(self, newItems = []) { 3 | const newLength = newItems.length; 4 | const oldLength = self.length; 5 | const lengthDiff = newLength - oldLength; 6 | 7 | for (let i = 0; i < newLength; i++) { 8 | self[i] = newItems[i]; 9 | } 10 | 11 | for (let i = 0; i < lengthDiff; i++) { 12 | delete self[i + newLength]; 13 | } 14 | 15 | self.length = newLength; 16 | 17 | return self; 18 | } 19 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_processrendering/checkalreadyrendered.js: -------------------------------------------------------------------------------- 1 | import defs from '../../_core/defs'; 2 | import seempleError from '../../_helpers/seempleerror'; 3 | 4 | // checks is item already rendered in an array 5 | // selfDef is given instead of itself (array) for perf optimisation 6 | export default function checkAlreadyRendered({ 7 | item, 8 | selfDef 9 | }) { 10 | const itemDef = defs.get(item); 11 | const { id: selfId } = selfDef; 12 | 13 | // if item object is defined in object defs 14 | if (itemDef) { 15 | const { renderedInArrays } = itemDef; 16 | 17 | // if item's node is already rendered for an array 18 | // then throw an error 19 | if (renderedInArrays && renderedInArrays[selfId]) { 20 | throw seempleError('array:add_render_twice'); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_processrendering/getalreadyrendered.js: -------------------------------------------------------------------------------- 1 | import defs from '../../_core/defs'; 2 | 3 | // returns already rendered node of an object in given array 4 | // selfDef is given instead of itself (array) for perf optimisation 5 | export default function getAlreadyRendered({ 6 | item, 7 | selfDef 8 | }) { 9 | const itemDef = defs.get(item); 10 | const { id: selfId } = selfDef; 11 | 12 | // if item object is defined in object defs 13 | if (itemDef) { 14 | const { renderedInArrays } = itemDef; 15 | 16 | // if item's node is already rendered for an array then return it 17 | if (renderedInArrays && renderedInArrays[selfId]) { 18 | const node = renderedInArrays[selfId]; 19 | return node.__replacedByNode || node; 20 | } 21 | } 22 | 23 | return undefined; 24 | } 25 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_processrendering/processpush.js: -------------------------------------------------------------------------------- 1 | import renderItemNode from './renderitemnode'; 2 | import triggerOne from '../../trigger/_triggerone'; 3 | import checkAlreadyRendered from './checkalreadyrendered'; 4 | import forEach from '../../_helpers/foreach'; 5 | 6 | // this function renders inserted items if possible when push method is called 7 | export default function processPush({ 8 | self, 9 | selfDef, 10 | eventOptions, 11 | container 12 | }) { 13 | const { added, silent } = eventOptions; 14 | 15 | forEach(added, (item) => { 16 | if (item && typeof item === 'object') { 17 | // if a node of an item is already rendered then throw an error 18 | checkAlreadyRendered({ 19 | item, 20 | selfDef 21 | }); 22 | 23 | // render 24 | const { node, itemEventOptions } = renderItemNode({ 25 | selfDef, 26 | self, 27 | item, 28 | eventOptions 29 | }); 30 | 31 | if (node) { 32 | container.appendChild(node); 33 | if (!silent) { 34 | triggerOne(item, 'afterrender', itemEventOptions); 35 | } 36 | } 37 | } 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_processrendering/processremove.js: -------------------------------------------------------------------------------- 1 | import defs from '../../_core/defs'; 2 | import forEach from '../../_helpers/foreach'; 3 | 4 | // this function removes DOM nodes of removed items 5 | // called on splice, pull, pop and shift 6 | export default function processRemove({ 7 | // self, 8 | selfDef, 9 | eventOptions, 10 | container 11 | }) { 12 | const { removed } = eventOptions; 13 | const { id: selfId } = selfDef; 14 | forEach(removed, (item) => { 15 | if (item && typeof item === 'object') { 16 | const itemDef = defs.get(item); 17 | if (itemDef) { 18 | const { renderedInArrays } = itemDef; 19 | const node = renderedInArrays && renderedInArrays[selfId]; 20 | if (node) { 21 | delete renderedInArrays[selfId]; 22 | container.removeChild(node); 23 | } 24 | } 25 | } 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_processrendering/processrerender.js: -------------------------------------------------------------------------------- 1 | import getAlreadyRendered from './getalreadyrendered'; 2 | import renderItemNode from './renderitemnode'; 3 | import triggerOne from '../../trigger/_triggerone'; 4 | 5 | // this function re-inserts rendered DOM nodes of items 6 | // if they are rendered and forceRerender is falsy 7 | // and renders array items from scratch if they aren't rendered yet or forceRerender is truthy 8 | export default function processRerender({ 9 | self, 10 | selfDef, 11 | eventOptions, 12 | container 13 | }) { 14 | const { forceRerender, silent } = eventOptions; 15 | 16 | // iterate over all items 17 | for (let i = 0; i < self.length; i++) { 18 | const item = self[i]; 19 | if (item && typeof item === 'object') { 20 | const alreadyRenderedNode = getAlreadyRendered({ 21 | item, 22 | selfDef 23 | }); 24 | 25 | // if item is already rendered and forceRerender is falsy then re-insert DOM node 26 | // go to the next cycle iteration then 27 | if (!forceRerender && alreadyRenderedNode) { 28 | container.appendChild(alreadyRenderedNode); 29 | continue; 30 | } 31 | 32 | // node removal is called when an item is rendered 33 | // and forceRerender is truty 34 | if (alreadyRenderedNode) { 35 | if (container.contains(alreadyRenderedNode)) { 36 | container.removeChild(alreadyRenderedNode); 37 | } 38 | } 39 | 40 | // render new node 41 | const { node, itemEventOptions } = renderItemNode({ 42 | selfDef, 43 | self, 44 | item, 45 | eventOptions 46 | }); 47 | 48 | if (node) { 49 | container.appendChild(node); 50 | 51 | if (!silent) { 52 | triggerOne(item, 'afterrender', itemEventOptions); 53 | } 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_processrendering/processsort.js: -------------------------------------------------------------------------------- 1 | import getAlreadyRendered from './getalreadyrendered'; 2 | import forEach from '../../_helpers/foreach'; 3 | 4 | 5 | // this function gets called when array is sorted (via sort, orderBy or reverse) 6 | export default function processSort({ 7 | self, 8 | selfDef, 9 | // eventOptions, 10 | container 11 | }) { 12 | // just re-insert rendered nodes in new order 13 | forEach(self, (item) => { 14 | if (item && typeof item === 'object') { 15 | const node = getAlreadyRendered({ 16 | item, 17 | selfDef 18 | }); 19 | 20 | if (node) { 21 | container.appendChild(node); 22 | } 23 | } 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_processrendering/processspliceadd.js: -------------------------------------------------------------------------------- 1 | import renderItemNode from './renderitemnode'; 2 | import triggerOne from '../../trigger/_triggerone'; 3 | import forEach from '../../_helpers/foreach'; 4 | import checkAlreadyRendered from './checkalreadyrendered'; 5 | import getAlreadyRendered from './getalreadyrendered'; 6 | 7 | // the function handles rendering of added items passed as third and rest arguments to splice method 8 | export default function processSpliceAdd({ 9 | self, 10 | selfDef, 11 | eventOptions, 12 | container 13 | }) { 14 | const { added, silent } = eventOptions; 15 | const nextIndex = self.lastIndexOf(added[added.length - 1]) + 1; 16 | const next = self[nextIndex]; 17 | let nextNode; 18 | 19 | // get a node of an item which is placed next to the last added item 20 | // it is needed to insert newly rendered items before 21 | if (next && typeof next === 'object') { 22 | nextNode = getAlreadyRendered({ 23 | item: next, 24 | selfDef 25 | }); 26 | } 27 | 28 | forEach(added, (item) => { 29 | if (item && typeof item === 'object') { 30 | // throw an error if node of an item is alread rendered 31 | checkAlreadyRendered({ 32 | item, 33 | selfDef 34 | }); 35 | 36 | const { node, itemEventOptions } = renderItemNode({ 37 | selfDef, 38 | self, 39 | item, 40 | eventOptions 41 | }); 42 | 43 | if (node) { 44 | if (nextNode) { 45 | container.insertBefore(node, nextNode); 46 | } else { 47 | container.appendChild(node); 48 | } 49 | 50 | if (!silent) { 51 | triggerOne(item, 'afterrender', itemEventOptions); 52 | } 53 | } 54 | } 55 | }); 56 | } 57 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_processrendering/processunshift.js: -------------------------------------------------------------------------------- 1 | import renderItemNode from './renderitemnode'; 2 | import triggerOne from '../../trigger/_triggerone'; 3 | import checkAlreadyRendered from './checkalreadyrendered'; 4 | 5 | // this function renders inserted items if possible when unshift or push method is called 6 | export default function processUnshift({ 7 | self, 8 | selfDef, 9 | eventOptions, 10 | container 11 | }) { 12 | const { added, silent } = eventOptions; 13 | 14 | // iterate over all added items in opposite order 15 | for (let i = added.length - 1; i + 1; i--) { 16 | const item = added[i]; 17 | if (item && typeof item === 'object') { 18 | // if a node of an item is already rendered then throw an error 19 | checkAlreadyRendered({ 20 | item, 21 | selfDef 22 | }); 23 | 24 | const { node, itemEventOptions } = renderItemNode({ 25 | selfDef, 26 | self, 27 | item, 28 | eventOptions 29 | }); 30 | 31 | if (node) { 32 | if (container.firstChild) { 33 | container.insertBefore(node, container.firstChild); 34 | } else { 35 | container.appendChild(node); 36 | } 37 | 38 | if (!silent) { 39 | triggerOne(item, 'afterrender', itemEventOptions); 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_prototype.js: -------------------------------------------------------------------------------- 1 | import assign from '../_helpers/assign'; 2 | import _afterInit from './_afterinit'; 3 | import mediateItem from './mediateitem'; 4 | import orderBy from './orderby'; 5 | import pull from './pull'; 6 | import recreate from './recreate'; 7 | import rerender from './rerender'; 8 | import restore from './restore'; 9 | import toJSON from './tojson'; 10 | import pseudoNativeMethods from './_pseudonativemethods'; 11 | import iterator from './iterator'; 12 | 13 | const symbolIterator = typeof Symbol === 'function' ? Symbol.iterator : '@@iterator'; 14 | 15 | export default assign({ 16 | _afterInit, 17 | mediateItem, 18 | orderBy, 19 | pull, 20 | recreate, 21 | rerender, 22 | restore, 23 | toJSON, 24 | length: 0, 25 | isSeempleArray: true, 26 | [symbolIterator]: iterator 27 | }, pseudoNativeMethods); 28 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_pseudonativemethods/concat.js: -------------------------------------------------------------------------------- 1 | import apply from '../../_helpers/apply'; 2 | import forEach from '../../_helpers/foreach'; 3 | 4 | 5 | // the method works just like Array.prototype.concat but 6 | // - flattens both Array and Seemple.Array 7 | // - returns Seemple.Array 8 | export default function concat() { 9 | // fix circular dependency issue 10 | const SeempleArray = require('../').default; 11 | 12 | const args = Array(arguments.length); 13 | 14 | // convert all instances of Seemple.Array to Array 15 | forEach(arguments, (arg, index) => { 16 | if (arg && typeof arg === 'object' && arg.isSeempleArray) { 17 | args[index] = arg.toJSON(false); 18 | } else { 19 | args[index] = arg; 20 | } 21 | }); 22 | 23 | // call original concat method 24 | const nativeCallResult = apply(Array.prototype.concat, this.toJSON(false), args); 25 | 26 | // convert returned value to Seemple.Array 27 | const result = new SeempleArray(); 28 | forEach(nativeCallResult, (item, index) => { 29 | result[index] = item; 30 | }); 31 | 32 | result.length = nativeCallResult.length; 33 | 34 | return result; 35 | } 36 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_pseudonativemethods/createaddingmethod.js: -------------------------------------------------------------------------------- 1 | import initSeemple from '../../_core/init'; 2 | import reportModified from '../_reportmodified'; 3 | import assign from '../../_helpers/assign'; 4 | 5 | // creates methods: push, unshift, push_, unshift_ 6 | export default function createAddingMethod(name, hasOptions) { 7 | return function pseudoNativeMethod() { 8 | const { itemMediator } = initSeemple(this); 9 | // +hasOptions is converted to 0 or 1 depending on its value (false/true) 10 | const argsLength = arguments.length - +hasOptions; 11 | const args = Array(argsLength); 12 | const givenEventOptions = hasOptions ? arguments[arguments.length - 1] : null; 13 | const useMediator = typeof itemMediator === 'function' 14 | && (!givenEventOptions || !givenEventOptions.skipItemMediator); 15 | const isPush = name === 'push'; 16 | let { length } = this; 17 | 18 | // if no arguments are passed 19 | if (!argsLength) { 20 | return length; 21 | } 22 | 23 | // convert arguments to array and call item mediator on every item if it's possible 24 | for (let i = 0; i < argsLength; i++) { 25 | const arg = arguments[i]; 26 | if (useMediator) { 27 | const index = isPush ? i + length : i; 28 | args[i] = itemMediator(arg, index); 29 | } else { 30 | args[i] = arg; 31 | } 32 | } 33 | 34 | if (isPush) { 35 | // insert new items to the end of array 36 | for (let i = 0; i < argsLength; i++) { 37 | this[length + i] = args[i]; 38 | } 39 | } else { 40 | // move current items to new indexes 41 | for (let i = length - 1; i >= 0; i--) { 42 | this[argsLength + i] = this[i]; 43 | } 44 | // insert new items to the begin of array 45 | for (let i = 0; i < argsLength; i++) { 46 | this[i] = args[i]; 47 | } 48 | } 49 | 50 | // update length 51 | length += argsLength; 52 | this.length = length; 53 | 54 | const eventOptions = { 55 | method: name, 56 | self: this, 57 | added: args, 58 | removed: [] 59 | }; 60 | 61 | // extend event options by custom event options if they are given 62 | if (hasOptions) { 63 | if (givenEventOptions && typeof givenEventOptions === 'object') { 64 | assign(eventOptions, givenEventOptions); 65 | } 66 | } 67 | 68 | reportModified(this, eventOptions); 69 | 70 | return length; 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_pseudonativemethods/createcopywithin.js: -------------------------------------------------------------------------------- 1 | import apply from '../../_helpers/apply'; 2 | import reportModified from '../_reportmodified'; 3 | import seempleError from '../../_helpers/seempleerror'; 4 | import assign from '../../_helpers/assign'; 5 | 6 | export default function createCopyWithin(hasOptions) { 7 | return function copyWithin() { 8 | const originalCopyWithin = Array.prototype.copyWithin; 9 | 10 | /* istanbul ignore if */ 11 | if (typeof originalCopyWithin !== 'function') { 12 | throw seempleError('array:nonexistent_method', { method: 'copyWithin' }); 13 | } 14 | // +hasOptions is converted to 0 or 1 depending on its value (false/true) 15 | const argsLength = arguments.length - +hasOptions; 16 | const args = Array(argsLength); 17 | const givenEventOptions = hasOptions ? arguments[arguments.length - 1] : null; 18 | 19 | for (let i = 0; i < argsLength; i++) { 20 | args[i] = arguments[i]; 21 | } 22 | 23 | apply(originalCopyWithin, this, args); 24 | 25 | const eventOptions = { 26 | method: 'copyWithin', 27 | self: this, 28 | added: [], 29 | removed: [] 30 | }; 31 | 32 | // extend event options by custom event options if they are given 33 | if (hasOptions) { 34 | if (givenEventOptions && typeof givenEventOptions === 'object') { 35 | assign(eventOptions, givenEventOptions); 36 | } 37 | } 38 | 39 | reportModified(this, eventOptions); 40 | 41 | return this; 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_pseudonativemethods/createfill.js: -------------------------------------------------------------------------------- 1 | import apply from '../../_helpers/apply'; 2 | import reportModified from '../_reportmodified'; 3 | import seempleError from '../../_helpers/seempleerror'; 4 | import assign from '../../_helpers/assign'; 5 | 6 | export default function createFill(hasOptions) { 7 | return function fill(value) { 8 | const originalFill = Array.prototype.fill; 9 | 10 | /* istanbul ignore if */ 11 | if (typeof originalFill !== 'function') { 12 | throw seempleError('array:nonexistent_method', { method: 'fill' }); 13 | } 14 | // +hasOptions is converted to 0 or 1 depending on its value (false/true) 15 | const argsLength = arguments.length - +hasOptions; 16 | const args = Array(argsLength); 17 | const givenEventOptions = hasOptions ? arguments[arguments.length - 1] : null; 18 | 19 | for (let i = 0; i < argsLength; i++) { 20 | args[i] = arguments[i]; 21 | } 22 | 23 | apply(originalFill, this, args); 24 | 25 | const eventOptions = { 26 | method: 'fill', 27 | self: this, 28 | added: [value], 29 | removed: [] 30 | }; 31 | 32 | // extend event options by custom event options if they are given 33 | if (hasOptions) { 34 | if (givenEventOptions && typeof givenEventOptions === 'object') { 35 | assign(eventOptions, givenEventOptions); 36 | } 37 | } 38 | 39 | reportModified(this, eventOptions); 40 | 41 | return this; 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_pseudonativemethods/createremovingmethod.js: -------------------------------------------------------------------------------- 1 | import initSeemple from '../../_core/init'; 2 | import reportModified from '../_reportmodified'; 3 | import assign from '../../_helpers/assign'; 4 | 5 | // creates removing method and returns it (pop, shift, pop_, shift_) 6 | export default function createRemovingMethod(name, hasOptions) { 7 | return function pseudoNativeMethod(givenEventOptions) { 8 | if (!this.length) { 9 | return undefined; 10 | } 11 | initSeemple(this); 12 | 13 | // call original method 14 | const returns = Array.prototype[name].call(this); 15 | const eventOptions = { 16 | method: name, 17 | self: this, 18 | added: [], 19 | removed: [returns] 20 | }; 21 | 22 | // extend event options by custom event options if they are given 23 | if (hasOptions) { 24 | if (givenEventOptions && typeof givenEventOptions === 'object') { 25 | assign(eventOptions, givenEventOptions); 26 | } 27 | } 28 | 29 | reportModified(this, eventOptions); 30 | 31 | return returns; 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_pseudonativemethods/createsortingmethod.js: -------------------------------------------------------------------------------- 1 | import initSeemple from '../../_core/init'; 2 | import reportModified from '../_reportmodified'; 3 | import assign from '../../_helpers/assign'; 4 | 5 | // creates sorting method and returns it (sort, reverse, sort_, reverse_) 6 | export default function createSortingMethod(name, hasOptions) { 7 | return function pseudoNativeMethod(sortCallback) { 8 | if (this.length < 2) return this; 9 | initSeemple(this); 10 | 11 | const givenEventOptions = hasOptions ? arguments[arguments.length - 1] : null; 12 | const method = Array.prototype[name]; 13 | 14 | const eventOptions = { 15 | method: name, 16 | self: this, 17 | added: [], 18 | removed: [] 19 | }; 20 | 21 | // call original method 22 | if (name === 'sort' && typeof sortCallback === 'function') { 23 | method.call(this, sortCallback); 24 | } else { 25 | method.call(this); 26 | } 27 | 28 | // extend event options by custom event options if they are given 29 | if (hasOptions) { 30 | if (givenEventOptions && typeof givenEventOptions === 'object') { 31 | assign(eventOptions, givenEventOptions); 32 | } 33 | } 34 | 35 | reportModified(this, eventOptions); 36 | 37 | return this; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_pseudonativemethods/createsplice.js: -------------------------------------------------------------------------------- 1 | import initSeemple from '../../_core/init'; 2 | import reportModified from '../_reportmodified'; 3 | import toSeempleArray from '../_toseemplearray'; 4 | import apply from '../../_helpers/apply'; 5 | import assign from '../../_helpers/assign'; 6 | 7 | // creates splice or splice_ method and returns it 8 | // TODO: Improve readability of createSplice function 9 | export default function createSplice(hasOptions) { 10 | return function pseudoNativeMethod() { 11 | const { itemMediator } = initSeemple(this); 12 | const functionArguments = arguments; 13 | const argsLength = functionArguments.length - +hasOptions; 14 | const args = Array(argsLength); 15 | const givenEventOptions = hasOptions 16 | ? functionArguments[functionArguments.length - 1] 17 | : null; 18 | const useMediator = typeof itemMediator === 'function' 19 | && (!givenEventOptions || !givenEventOptions.skipItemMediator); 20 | const added = []; 21 | let start = args[0]; 22 | const { length } = this; 23 | 24 | start = start < 0 ? length + start : start; 25 | 26 | // convert arguments to array and call item mediator on every new item if it's possible 27 | args[0] = functionArguments[0]; 28 | args[1] = functionArguments[1]; 29 | for (let i = 2; i < argsLength; i++) { 30 | const arg = functionArguments[i]; 31 | if (useMediator) { 32 | args[i] = itemMediator(arg, start + (i - 2)); 33 | } else { 34 | args[i] = arg; 35 | } 36 | 37 | added[i - 2] = args[i]; 38 | } 39 | 40 | // call original method 41 | // TODO: Change array manually in splice method for better performance 42 | const returns = apply(Array.prototype.splice, this, args); 43 | // removed items mean returned items 44 | const removed = returns; 45 | 46 | // if something is added or removed 47 | if (added.length || removed.length) { 48 | const eventOptions = { 49 | added, 50 | removed, 51 | method: 'splice', 52 | self: this 53 | }; 54 | 55 | // extend event options by custom event options if they are given 56 | if (hasOptions) { 57 | if (givenEventOptions && typeof givenEventOptions === 'object') { 58 | assign(eventOptions, givenEventOptions); 59 | } 60 | } 61 | 62 | reportModified(this, eventOptions); 63 | } 64 | 65 | return toSeempleArray(returns); 66 | }; 67 | } 68 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_pseudonativemethods/entries.js: -------------------------------------------------------------------------------- 1 | // returns pairs like [index, value] 2 | export default function values() { 3 | const { length } = this; 4 | const result = new Array(length); 5 | 6 | for (let i = 0; i < length; i++) { 7 | result[i] = [i, this[i]]; 8 | } 9 | 10 | return result; 11 | } 12 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_pseudonativemethods/index.js: -------------------------------------------------------------------------------- 1 | import createPseudoNativeMethod from './createpseudonativemethod'; 2 | import concat from './concat'; 3 | import keys from './keys'; 4 | import values from './values'; 5 | import entries from './entries'; 6 | 7 | const splitBySpaceReg = /\s+/; 8 | const methods = { 9 | concat, keys, values, entries 10 | }; 11 | 12 | `push pop unshift shift sort reverse splice map filter slice every some reduce reduceRight 13 | forEach join indexOf lastIndexOf copyWithin fill includes find findIndex` 14 | .split(splitBySpaceReg).forEach((name) => { 15 | methods[name] = createPseudoNativeMethod(name); 16 | }); 17 | 18 | 'push pop unshift shift sort reverse splice copyWithin fill' 19 | .split(splitBySpaceReg).forEach((name) => { 20 | methods[`${name}_`] = createPseudoNativeMethod(name, true); 21 | }); 22 | 23 | export default methods; 24 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_pseudonativemethods/keys.js: -------------------------------------------------------------------------------- 1 | // returns indexes 2 | export default function keys() { 3 | const { length } = this; 4 | const result = new Array(length); 5 | 6 | for (let i = 0; i < length; i++) { 7 | result[i] = i; 8 | } 9 | 10 | return result; 11 | } 12 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_pseudonativemethods/values.js: -------------------------------------------------------------------------------- 1 | // returns values 2 | export default function values() { 3 | const { length } = this; 4 | const result = new Array(length); 5 | 6 | for (let i = 0; i < length; i++) { 7 | result[i] = this[i]; 8 | } 9 | 10 | return result; 11 | } 12 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_staticmembers.js: -------------------------------------------------------------------------------- 1 | import from from './from'; // lol 2 | import of from './of'; 3 | 4 | export default { 5 | of, 6 | from 7 | }; 8 | -------------------------------------------------------------------------------- /packages/seemple/src/array/_toseemplearray.js: -------------------------------------------------------------------------------- 1 | import forEach from '../_helpers/foreach'; 2 | 3 | // converts array-like to Seemple.Array instance 4 | export default function toSeempleArray(arrayLike) { 5 | // fix circular dependency issue 6 | const SeempleArray = require('./').default; 7 | 8 | const result = new SeempleArray(arrayLike.length); 9 | 10 | forEach(arrayLike, (item, index) => { 11 | result[index] = item; 12 | }); 13 | 14 | return result; 15 | } 16 | -------------------------------------------------------------------------------- /packages/seemple/src/array/from.js: -------------------------------------------------------------------------------- 1 | import cheapRecreate from './_cheaprecreate'; 2 | 3 | // creates a new Seemple.Array instance from an array-like or iterable object 4 | export default function from(arrayLike, mapFn, thisArg) { 5 | // allow to inherit this method by child classes 6 | // require('./') fixes circular ref issue 7 | const ParentClass = typeof this === 'function' ? this : require('./').default; 8 | 9 | const result = new ParentClass(); 10 | const length = arrayLike.length; 11 | const arrayFrom = Array.from; 12 | let newItems; 13 | 14 | /* istanbul ignore else */ 15 | if (typeof arrayFrom === 'function') { 16 | // if Array.from exist, let it do all the job (work with iterable objects etc) 17 | newItems = arrayFrom(arrayLike, mapFn, thisArg); 18 | } else { 19 | // convert array-like object for older browsers 20 | // @IE 21 | newItems = Array(length); 22 | 23 | for (let i = 0; i < length; i++) { 24 | if (typeof mapFn === 'function') { 25 | newItems[i] = mapFn.call(thisArg, arrayLike[i], i, arrayLike); 26 | } else { 27 | newItems[i] = arrayLike[i]; 28 | } 29 | } 30 | } 31 | 32 | return cheapRecreate(result, newItems); 33 | } 34 | -------------------------------------------------------------------------------- /packages/seemple/src/array/index.js: -------------------------------------------------------------------------------- 1 | import Class from '../class'; 2 | import Seemple from '../seemple'; 3 | import instanceMembers from './_prototype'; 4 | import seempleError from '../_helpers/seempleerror'; 5 | import initSeemple from '../_core/init'; 6 | import staticMembers from './_staticmembers'; 7 | 8 | instanceMembers.extends = Seemple; 9 | 10 | instanceMembers.constructor = function SeempleArray(length) { 11 | if (!(this instanceof SeempleArray)) { 12 | throw seempleError('common:call_class'); 13 | } 14 | 15 | initSeemple(this); 16 | 17 | // repeat the same logic as for native Array 18 | if (arguments.length === 1 && typeof length === 'number') { 19 | this.length = length; 20 | } else if (arguments.length) { 21 | this.recreate(arguments, { 22 | silent: true, 23 | dontRender: true 24 | }); 25 | } 26 | 27 | // return is used to make possible to chain super() calls 28 | return this; 29 | }; 30 | 31 | const SeempleArray = Class(instanceMembers, staticMembers); 32 | 33 | export default SeempleArray; 34 | -------------------------------------------------------------------------------- /packages/seemple/src/array/iterator.js: -------------------------------------------------------------------------------- 1 | // Symbol.iterator of Seemple.Array instances 2 | export default function seempleArrayIterator() { 3 | let i = 0; 4 | 5 | return { 6 | next: () => { 7 | if (i > this.length - 1) { 8 | return { 9 | done: true 10 | }; 11 | } 12 | 13 | return { 14 | done: false, 15 | value: this[i++] // eslint-disable-line no-plusplus 16 | }; 17 | } 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /packages/seemple/src/array/mediateitem.js: -------------------------------------------------------------------------------- 1 | import initSeemple from '../_core/init'; 2 | 3 | // creates item mediator 4 | function createItemMediator({ 5 | arr, 6 | mediator 7 | }) { 8 | return function itemMediator(value, index) { 9 | // args: value, old value, index, array itself 10 | return mediator.call(arr, value, index, arr); 11 | }; 12 | } 13 | 14 | // defines a "type" of every array item 15 | export default function mediateItem(mediator) { 16 | const def = initSeemple(this); 17 | const { length } = this; 18 | 19 | // store itemMediator in object definition 20 | const itemMediator = def.itemMediator = createItemMediator({ 21 | arr: this, 22 | mediator 23 | }); 24 | 25 | // convert existing items 26 | for (let i = 0; i < length; i++) { 27 | this[i] = itemMediator(this[i], i); 28 | } 29 | 30 | return this; 31 | } 32 | -------------------------------------------------------------------------------- /packages/seemple/src/array/of.js: -------------------------------------------------------------------------------- 1 | import cheapRecreate from './_cheaprecreate'; 2 | import forEach from '../_helpers/foreach'; 3 | 4 | // creates a new Seemple.Array instance with a variable number of arguments, 5 | // regardless of number or type of the arguments 6 | export default function of() { 7 | // allow to inherit this method by child classes 8 | // require('./') fixes circular ref issue 9 | const ParentClass = typeof this === 'function' ? this : require('./').default; 10 | 11 | const result = new ParentClass(); 12 | const newItems = Array(arguments.length); 13 | 14 | forEach(arguments, (item, index) => { 15 | newItems[index] = arguments[index]; 16 | }); 17 | 18 | return cheapRecreate(result, newItems); 19 | } 20 | -------------------------------------------------------------------------------- /packages/seemple/src/array/orderby/_pureorderby.js: -------------------------------------------------------------------------------- 1 | // the function orders by given order data any array-like object 2 | export default function pureOrderBy(arr, givenKeys, orders) { 3 | if ('length' in arr && typeof arr === 'object') { 4 | const defaultOrder = 'asc'; 5 | let commonOrder; 6 | 7 | if (!(orders instanceof Array)) { 8 | commonOrder = orders || defaultOrder; 9 | } 10 | 11 | const { length } = arr; 12 | const result = Array(length); 13 | 14 | for (let i = 0; i < length; i++) { 15 | result[i] = arr[i]; 16 | } 17 | 18 | if (!givenKeys) { 19 | return result; 20 | } 21 | 22 | const keys = givenKeys instanceof Array ? givenKeys : [givenKeys]; 23 | 24 | return result.sort((a, b) => { 25 | if (a && b) { 26 | for (let i = 0; i < keys.length; i++) { 27 | const key = keys[i]; 28 | const order = (commonOrder || orders[i]) !== 'desc' ? -1 : 1; 29 | 30 | if (a[key] > b[key]) { 31 | return -order; 32 | } if (a[key] < b[key]) { 33 | return order; 34 | } 35 | } 36 | } 37 | 38 | return 0; 39 | }); 40 | } 41 | 42 | return []; 43 | } 44 | -------------------------------------------------------------------------------- /packages/seemple/src/array/orderby/index.js: -------------------------------------------------------------------------------- 1 | import cheapRecreate from '../_cheaprecreate'; 2 | import pureOrderBy from './_pureorderby'; 3 | import reportModified from '../_reportmodified'; 4 | 5 | // sorts by properties of items 6 | export default function orderBy(keys, orders, eventOptions = {}) { 7 | if (this.length > 1) { 8 | cheapRecreate(this, pureOrderBy(this, keys, orders)); 9 | 10 | reportModified(this, { 11 | method: 'sort', // makes possible to listen "sort" event 12 | self: this, 13 | added: [], 14 | removed: [], 15 | ...eventOptions 16 | }); 17 | } 18 | 19 | return this; 20 | } 21 | -------------------------------------------------------------------------------- /packages/seemple/src/array/pull.js: -------------------------------------------------------------------------------- 1 | import reportModified from './_reportmodified'; 2 | import seempleError from '../_helpers/seempleerror'; 3 | 4 | // removes array item by given index 5 | function shift(arr, index) { 6 | for (let i = index; i < arr.length; i++) { 7 | arr[i] = arr[i + 1]; 8 | } 9 | delete arr[arr.length - 1]; 10 | arr.length -= 1; 11 | } 12 | 13 | // finds array item that equals to given value and removes it 14 | // returns removed value 15 | function pullByValue(arr, value) { 16 | for (let i = 0; i < arr.length; i++) { 17 | if (arr[i] === value) { 18 | shift(arr, i); 19 | return value; 20 | } 21 | } 22 | 23 | return undefined; 24 | } 25 | 26 | // removes array item by given index if the index is not over array length 27 | // returns removed value 28 | function pullByIndex(arr, index) { 29 | if (index < arr.length) { 30 | const value = arr[index]; 31 | shift(arr, index); 32 | return value; 33 | } 34 | 35 | return undefined; 36 | } 37 | 38 | // removes an array item by index (if number is given) or by value (if object is given) 39 | export default function pull(toRemove, eventOptions = {}) { 40 | const typeofToRemove = typeof toRemove; 41 | let removed; 42 | 43 | if (toRemove && typeofToRemove === 'object') { 44 | removed = pullByValue(this, toRemove); 45 | } else if (typeofToRemove === 'number') { 46 | removed = pullByIndex(this, toRemove); 47 | } else { 48 | throw seempleError('pull:to_remove_type', { toRemove }); 49 | } 50 | 51 | if (typeof removed !== 'undefined') { 52 | reportModified(this, { 53 | method: 'pull', 54 | self: this, 55 | added: [], 56 | removed: [removed], 57 | ...eventOptions 58 | }); 59 | } 60 | 61 | return removed; 62 | } 63 | -------------------------------------------------------------------------------- /packages/seemple/src/array/recreate/_updateobject.js: -------------------------------------------------------------------------------- 1 | import forOwn from '../../_helpers/forown'; 2 | 3 | // updates one single object by new data 4 | // for Seemple.Array instance call recreate method 5 | // for Seemple.Object instance call setData method 6 | // for other objects just extend them by properties of data parameter 7 | export default function updateObject(instance, data) { 8 | if (instance.isSeempleArray) { 9 | instance.recreate(data); 10 | } else if (instance.isSeempleObject) { 11 | // QUESTION: Is it OK to just extend but not replace instance data? 12 | instance.setData(data); 13 | } else { 14 | forOwn(data, (value, key) => { 15 | instance[key] = value; 16 | }); 17 | } 18 | 19 | return instance; 20 | } 21 | -------------------------------------------------------------------------------- /packages/seemple/src/array/recreate/_updatetracked.js: -------------------------------------------------------------------------------- 1 | import updateObject from './_updateobject'; 2 | 3 | // the function gets called to update new items passed to recreate method when trackBy is present 4 | // TODO: Throw an error when two or more items of one array has the same value of trackBy 5 | export default function updateTracked({ 6 | givenNewItems, 7 | arr, 8 | trackBy 9 | }) { 10 | const newLength = givenNewItems.length; 11 | const oldLength = arr.length; 12 | const newItems = Array(newLength); 13 | 14 | if (trackBy === '$index') { 15 | // simply update items with the same index 16 | for (let i = 0; i < newLength; i++) { 17 | const item = arr[i]; 18 | const newItem = givenNewItems[i]; 19 | 20 | if ( 21 | item && typeof item === 'object' 22 | && newItem && typeof newItem === 'object' 23 | ) { 24 | newItems[i] = updateObject(item, newItem); 25 | } else { 26 | newItems[i] = newItem; 27 | } 28 | } 29 | } else { 30 | const trackMap = {}; 31 | 32 | // fill trackMap object where keys are values of trackBy and values are corresponding items 33 | for (let i = 0; i < oldLength; i++) { 34 | const item = arr[i]; 35 | 36 | if (item && typeof item === 'object') { 37 | if (trackBy in item) { 38 | trackMap[item[trackBy]] = item; 39 | } 40 | } 41 | } 42 | 43 | for (let i = 0; i < newLength; i++) { 44 | const newItem = givenNewItems[i]; 45 | 46 | if (newItem && typeof newItem === 'object') { 47 | const item = arr[i]; 48 | 49 | if (item && typeof item === 'object' && newItem[trackBy] in trackMap) { 50 | // if an item exists at trackMap then update it 51 | newItems[i] = updateObject(trackMap[newItem[trackBy]], newItem); 52 | } else { 53 | // if not then use new value as is 54 | newItems[i] = newItem; 55 | } 56 | } else { 57 | // newItem is not an object 58 | newItems[i] = newItem; 59 | } 60 | } 61 | } 62 | 63 | return newItems; 64 | } 65 | -------------------------------------------------------------------------------- /packages/seemple/src/array/rerender.js: -------------------------------------------------------------------------------- 1 | import processRendering from './_processrendering'; 2 | 3 | // rerenders not rendered items in an array 4 | // force rerender when forceRerender event option is truthy 5 | export default function rerender(eventOptions = {}) { 6 | const { renderIfPossible = true } = this; 7 | if (renderIfPossible) { 8 | processRendering({ 9 | self: this, 10 | eventOptions: { 11 | method: 'rerender', 12 | added: [], 13 | removed: [], 14 | ...eventOptions 15 | } 16 | }); 17 | } 18 | 19 | return this; 20 | } 21 | -------------------------------------------------------------------------------- /packages/seemple/src/array/restore.js: -------------------------------------------------------------------------------- 1 | import initSeemple from '../_core/init'; 2 | import seempleError from '../_helpers/seempleerror'; 3 | import forEach from '../_helpers/foreach'; 4 | import bindNode from '../bindnode'; 5 | import triggerOne from '../trigger/_triggerone'; 6 | import getNodes from '../bindnode/_getnodes'; 7 | 8 | // restores Seemple.Array from external nodes 9 | export default function restore(selector, eventOptions = {}) { 10 | const selfDef = initSeemple(this); 11 | const { Model } = this; 12 | const { silent } = eventOptions; 13 | const newItems = []; 14 | let nodes; 15 | 16 | if (typeof selector === 'string') { 17 | // get nodes by selector 18 | nodes = getNodes(this, selector); 19 | } else { 20 | // get nodes from rendering container 21 | const container = this.nodes.container || this.nodes.sandbox; 22 | 23 | if (container) { 24 | nodes = container.children; 25 | } else { 26 | // no container is bound, throw an error 27 | throw seempleError('restore:no_nodes'); 28 | } 29 | } 30 | 31 | forEach(nodes, (node, index) => { 32 | const item = Model ? new Model({}, this, index) : {}; // create new item 33 | const { bindRenderedAsSandbox } = item; 34 | const itemDef = initSeemple(item); 35 | 36 | itemDef.renderedInArrays = { 37 | [selfDef.id]: node 38 | }; 39 | 40 | if (bindRenderedAsSandbox !== false) { 41 | bindNode(item, 'sandbox', node, null, eventOptions); 42 | } 43 | 44 | if (!silent) { 45 | // trigger needed events 46 | const itemEventOptions = { 47 | node, 48 | self: item, 49 | parentArray: this 50 | }; 51 | 52 | const { onRender } = item; 53 | const { onItemRender } = this; 54 | 55 | if (onRender) { 56 | onRender.call(item, itemEventOptions); 57 | } 58 | 59 | if (onItemRender) { 60 | onItemRender.call(this, item, itemEventOptions); 61 | } 62 | 63 | triggerOne(item, 'render', itemEventOptions); 64 | 65 | // call afterrender immediately because a node already exists in DOM tree 66 | triggerOne(item, 'afterrender', itemEventOptions); 67 | } 68 | 69 | newItems.push(item); 70 | }); 71 | 72 | // recreate an array but don't render newly added items 73 | return this.recreate(newItems, { 74 | dontRender: true, 75 | ...eventOptions 76 | }); 77 | } 78 | -------------------------------------------------------------------------------- /packages/seemple/src/array/tojson.js: -------------------------------------------------------------------------------- 1 | import forEach from '../_helpers/foreach'; 2 | 3 | // converts Seemple.Array instance to ordinary array 4 | export default function toJSON(recursive = true) { 5 | const result = new Array(this.length); 6 | 7 | forEach(this, (item, index) => { 8 | // when recursive is true and when an item has toJSON method then call it recusively 9 | if (recursive && item && typeof item.toJSON === 'function') { 10 | result[index] = item.toJSON(true); 11 | } else { 12 | result[index] = item; 13 | } 14 | }); 15 | 16 | return result; 17 | } 18 | -------------------------------------------------------------------------------- /packages/seemple/src/binders/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | html, display, className, prop, attr, 3 | text, style, dataset, existence 4 | } from 'common-binders'; 5 | 6 | import input from './input'; 7 | import output from './output'; 8 | import textarea from './textarea'; 9 | import select from './select'; 10 | import progress from './progress'; 11 | 12 | export { 13 | html, 14 | display, 15 | className, 16 | prop, 17 | attr, 18 | input, 19 | output, 20 | textarea, 21 | select, 22 | progress, 23 | text, 24 | style, 25 | dataset, 26 | existence 27 | }; 28 | -------------------------------------------------------------------------------- /packages/seemple/src/binders/input.js: -------------------------------------------------------------------------------- 1 | // returns a binder for input element based on its type 2 | export default function input(type) { 3 | let on; 4 | switch (type) { 5 | case 'checkbox': 6 | return { 7 | on: 'click keyup', 8 | getValue() { 9 | return this.checked; 10 | }, 11 | setValue(value) { 12 | this.checked = value; 13 | } 14 | }; 15 | case 'radio': 16 | return { 17 | on: 'click keyup', 18 | getValue() { 19 | return this.value; 20 | }, 21 | setValue(value) { 22 | this.checked = typeof value !== 'undefined' && this.value === value; 23 | } 24 | }; 25 | case 'submit': 26 | case 'button': 27 | case 'image': 28 | case 'reset': 29 | return {}; 30 | case 'hidden': 31 | on = null; 32 | break; 33 | case 'file': 34 | on = 'change'; 35 | break; 36 | 37 | /* 38 | case 'text': 39 | case 'password': 40 | case 'date': 41 | case 'datetime': 42 | case 'datetime-local': 43 | case 'month': 44 | case 'time': 45 | case 'week': 46 | case 'range': 47 | case 'color': 48 | case 'search': 49 | case 'email': 50 | case 'tel': 51 | case 'url': 52 | case 'file': 53 | case 'number': */ 54 | default: // other future (HTML6+) inputs 55 | on = 'input'; 56 | } 57 | 58 | return { 59 | on, 60 | getValue() { 61 | return this.value; 62 | }, 63 | setValue(value) { 64 | this.value = value; 65 | } 66 | }; 67 | } 68 | -------------------------------------------------------------------------------- /packages/seemple/src/binders/output.js: -------------------------------------------------------------------------------- 1 | // returns a binder for output element 2 | export default function output() { 3 | return { 4 | on: null, 5 | getValue() { 6 | return this.value || this.textContent; 7 | }, 8 | setValue(value) { 9 | const property = 'form' in this ? 'value' : 'textContent'; 10 | this[property] = value === null ? '' : `${value}`; 11 | } 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /packages/seemple/src/binders/progress.js: -------------------------------------------------------------------------------- 1 | import input from './input'; 2 | 3 | // returns a binder for textarea element 4 | export default function progress() { 5 | return input(); 6 | } 7 | -------------------------------------------------------------------------------- /packages/seemple/src/binders/select.js: -------------------------------------------------------------------------------- 1 | // returns a binder for select element 2 | export default function select(multiple) { 3 | if (multiple) { 4 | return { 5 | on: 'change', 6 | getValue() { 7 | const { options } = this; 8 | const result = []; 9 | 10 | for (let i = 0; options.length > i; i++) { 11 | if (options[i].selected) { 12 | result.push(options[i].value); 13 | } 14 | } 15 | 16 | return result; 17 | }, 18 | setValue(givenValue) { 19 | const { options } = this; 20 | const value = typeof givenValue === 'string' ? [givenValue] : givenValue; 21 | for (let i = options.length - 1; i >= 0; i--) { 22 | options[i].selected = ~value.indexOf(options[i].value); 23 | } 24 | } 25 | }; 26 | } 27 | 28 | return { 29 | on: 'change', 30 | getValue() { 31 | return this.value; 32 | }, 33 | setValue(value) { 34 | this.value = value; 35 | 36 | if (!value) { 37 | const { options } = this; 38 | for (let i = options.length - 1; i >= 0; i--) { 39 | if (!options[i].value) { 40 | options[i].selected = true; 41 | break; 42 | } 43 | } 44 | } 45 | } 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /packages/seemple/src/binders/textarea.js: -------------------------------------------------------------------------------- 1 | import input from './input'; 2 | 3 | // returns a binder for textarea element 4 | export default function textarea() { 5 | // textarea behaves just like text input 6 | return input('text'); 7 | } 8 | -------------------------------------------------------------------------------- /packages/seemple/src/bindnode/_createbindingswitcher.js: -------------------------------------------------------------------------------- 1 | import unbindNode from '../unbindnode'; 2 | 3 | // returns a function which re-adds binding when object branch is changed 4 | // the function is called by bindNode when something like 5 | // 'foo.bar.baz' is passed to it as key argument value 6 | // this is one of the hardest things in the framework to understand 7 | export default function createBindingSwitcher({ 8 | object, 9 | deepPath, 10 | $nodes, 11 | binder, 12 | eventOptions, 13 | bindNode 14 | }) { 15 | return function bindingSwitcher(changeEvent = {}) { 16 | const deepPathLength = deepPath.length; 17 | const lastDeepPathItem = deepPath[deepPathLength - 1]; 18 | const { 19 | value, // new value of a branch 20 | previousValue, // previous value of a branch 21 | restPath // path starting currently changed branch (passed by addTreeListener) 22 | } = changeEvent; 23 | let target; // an object to call bindNode 24 | let previousTarget; // an object to call unbindNode 25 | 26 | 27 | if (value && typeof value === 'object' && restPath) { 28 | // if rest path is given and new value is an object 29 | target = value; 30 | for (let i = 0; i < restPath.length; i++) { 31 | target = target[restPath[i]]; 32 | if (!target) { 33 | break; 34 | } 35 | } 36 | } else { 37 | // if rest path is not given 38 | target = object; 39 | for (let i = 0; i < deepPathLength - 1; i++) { 40 | target = target[deepPath[i]]; 41 | if (!target) { 42 | break; 43 | } 44 | } 45 | } 46 | 47 | // if rest path is given and previous value is an object 48 | if (previousValue && typeof previousValue === 'object' && restPath) { 49 | previousTarget = previousValue; 50 | for (let i = 0; i < restPath.length; i++) { 51 | previousTarget = previousTarget[restPath[i]]; 52 | if (!previousTarget) { 53 | break; 54 | } 55 | } 56 | } 57 | 58 | // add binding for new target 59 | if (target && typeof target === 'object') { 60 | bindNode(target, lastDeepPathItem, $nodes, binder, eventOptions); 61 | } 62 | 63 | // remove binding for previously used object 64 | if (previousTarget && typeof previousTarget === 'object') { 65 | unbindNode(previousTarget, lastDeepPathItem, $nodes); 66 | } 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /packages/seemple/src/bindnode/_createnodehandler.js: -------------------------------------------------------------------------------- 1 | import is from '../_helpers/is'; 2 | import set from '../set'; 3 | 4 | // returns a function which called when bound node state is changed (eg DOM event is fired) 5 | export default function createNodeHandler({ 6 | object, 7 | key, 8 | node, 9 | propDef, 10 | binder, 11 | bindingOptions 12 | }) { 13 | return function nodeHandler(domEvent = {}) { 14 | // nodeHandler.disabled = true is set in unbindNode 15 | // we cannot "turn off" binder.on when its value is a function 16 | // developer needs to clean memory ("turn off" callback) manualy in binder.destroy 17 | if (nodeHandler.disabled) { 18 | return; 19 | } 20 | 21 | const previousValue = propDef.value; 22 | const { 23 | which, target, ctrlKey, altKey 24 | } = domEvent; 25 | const { getValue } = binder; 26 | const value = getValue.call(node, { 27 | previousValue, 28 | domEvent, 29 | originalEvent: domEvent.originalEvent || domEvent, // jQuery thing 30 | // will throw "preventDefault is not a function" when domEvent is empty object 31 | preventDefault: () => domEvent.preventDefault(), 32 | // will throw "stopPropagation is not a function" when domEvent is empty object 33 | stopPropagation: () => domEvent.stopPropagation(), 34 | which, 35 | target, 36 | ctrlKey, 37 | altKey, 38 | ...bindingOptions 39 | }); 40 | 41 | if (!is(value, previousValue)) { 42 | set(object, key, value, { 43 | fromNode: true, 44 | // the following properties are needed to avoid circular changes 45 | // they are used at objectHandler 46 | changedNode: node, 47 | onChangeValue: value, 48 | binder 49 | }); 50 | } 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /packages/seemple/src/bindnode/_createobjecthandler.js: -------------------------------------------------------------------------------- 1 | // returns a function which is called when property value is changed 2 | export default function createObjectHandler({ 3 | node, 4 | propDef, 5 | binder, 6 | bindingOptions 7 | }) { 8 | return function objectHandler(eventOptions = {}) { 9 | const { value } = propDef; 10 | const { onChangeValue, changedNode, binder: evtBinder } = eventOptions; 11 | const { setValue } = binder; 12 | // dirty hack for https://github.com/finom/seemple/issues/19 13 | const dirtyHackValue = onChangeValue === 'string' && typeof value === 'number' 14 | ? `${value}` : value; 15 | 16 | // don't call setValue if a property is changed via getValue of the same binder 17 | if (changedNode === node && onChangeValue === dirtyHackValue && evtBinder === binder) { 18 | return; 19 | } 20 | 21 | setValue.call(node, value, { 22 | value, 23 | ...bindingOptions 24 | }); 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /packages/seemple/src/bindnode/_getnodes.js: -------------------------------------------------------------------------------- 1 | import selectNodes from './_selectnodes'; 2 | import dom from '../_dom'; 3 | 4 | const htmlReg = / 1) { 31 | isDelegated = true; 32 | // TODO: Avoid collisions with bindings by using another event name 33 | // ... instead of _change:tree:xxx 34 | addTreeListener(sourceObject, deepPath, calcHandler); 35 | } else { 36 | exactKey = true; 37 | } 38 | } 39 | 40 | 41 | if (exactKey) { 42 | // normal handler 43 | addListener(sourceObject, `_change:deps:${sourceKey}`, calcHandler); 44 | } 45 | 46 | allSources.push({ 47 | sourceKey, 48 | sourceObject, 49 | isDelegated 50 | }); 51 | } 52 | -------------------------------------------------------------------------------- /packages/seemple/src/calc/_createcalchandler.js: -------------------------------------------------------------------------------- 1 | import set from '../set'; 2 | import deepFind from '../_helpers/deepfind'; 3 | import forEach from '../_helpers/foreach'; 4 | import apply from '../_helpers/apply'; 5 | 6 | // creates event handler for target object which will be fired when a source is changed 7 | export default function createCalcHandler({ 8 | object, 9 | eventOptions, 10 | allSources, 11 | target, 12 | def, 13 | handler 14 | }) { 15 | return function calcHandler(changeEvent = {}) { 16 | const values = []; 17 | const { protector = {} } = changeEvent; 18 | const protectKey = target + def.id; 19 | const { promiseCalc } = eventOptions; 20 | const setEventOptions = { 21 | protector, 22 | ...eventOptions, 23 | ...changeEvent 24 | }; 25 | 26 | if (protectKey in protector) { 27 | return; 28 | } 29 | 30 | protector[protectKey] = true; 31 | 32 | forEach(allSources, ({ 33 | sourceObject, 34 | sourceKey, 35 | isDelegated 36 | }) => { 37 | const value = isDelegated ? deepFind(sourceObject, sourceKey) : sourceObject[sourceKey]; 38 | values.push(value); 39 | }); 40 | 41 | let targetValue = apply(handler, object, values); 42 | 43 | if (promiseCalc) { 44 | if (!(targetValue instanceof Promise)) { 45 | targetValue = Promise.resolve(targetValue); 46 | } 47 | 48 | targetValue 49 | .then((promiseResult) => set(object, target, promiseResult, setEventOptions)) 50 | .catch((e) => { 51 | throw Error(e); 52 | }); 53 | } else { 54 | set(object, target, targetValue, setEventOptions); 55 | } 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /packages/seemple/src/chain.js: -------------------------------------------------------------------------------- 1 | import checkObjectType from './_helpers/checkobjecttype'; 2 | import forEach from './_helpers/foreach'; 3 | import * as universalMethods from './seemple/_universalmethods'; 4 | import Class from './class'; 5 | import apply from './_helpers/apply'; 6 | 7 | // create a prototype of ChainClass 8 | // store target object at "object" property 9 | const prototype = { 10 | constructor(object) { 11 | this.object = object; 12 | } 13 | }; 14 | 15 | const methodNames = Object.keys(universalMethods); 16 | 17 | // iterate over all universal methods 18 | for (let i = 0; i < methodNames.length; i++) { 19 | const methodName = methodNames[i]; 20 | const method = universalMethods[methodName]; 21 | 22 | // create every chained method 23 | prototype[methodName] = function chainedMethod() { 24 | const args = [this.object]; 25 | 26 | forEach(arguments, (argument) => { 27 | args.push(argument); 28 | }); 29 | 30 | apply(method, undefined, args); 31 | 32 | // returning this is important for chained calls 33 | return this; 34 | }; 35 | } 36 | 37 | const ChainClass = Class(prototype); 38 | 39 | // the function allows to chain static function calls on any object 40 | export default function chain(object) { 41 | // check for type and throw an error if it is not an object and is not a function 42 | checkObjectType(object, 'chain'); 43 | 44 | return new ChainClass(object); 45 | } 46 | -------------------------------------------------------------------------------- /packages/seemple/src/defaultbinders.js: -------------------------------------------------------------------------------- 1 | import input from './binders/input'; 2 | import textarea from './binders/textarea'; 3 | import select from './binders/select'; 4 | import progress from './binders/progress'; 5 | import output from './binders/output'; 6 | 7 | // defaultBinders collection by default contains only one function-checker 8 | export default [(node) => { 9 | switch (node.tagName) { 10 | case 'INPUT': 11 | return input(node.type); 12 | case 'TEXTAREA': 13 | return textarea(); 14 | case 'SELECT': 15 | return select(node.multiple); 16 | case 'PROGRESS': 17 | return progress(); 18 | case 'OUTPUT': 19 | return output(); 20 | default: 21 | return null; 22 | } 23 | }]; 24 | -------------------------------------------------------------------------------- /packages/seemple/src/index.js: -------------------------------------------------------------------------------- 1 | const Seemple = require('./seemple').default; 2 | const SeempleArray = require('./array').default; 3 | const SeempleObject = require('./object').default; 4 | 5 | Seemple.Object = SeempleObject; 6 | Seemple.Array = SeempleArray; 7 | 8 | module.exports = Seemple; 9 | -------------------------------------------------------------------------------- /packages/seemple/src/instantiate.js: -------------------------------------------------------------------------------- 1 | import checkObjectType from './_helpers/checkobjecttype'; 2 | import forOwn from './_helpers/forown'; 3 | import forEach from './_helpers/foreach'; 4 | import assign from './_helpers/assign'; 5 | import mediate from './mediate'; 6 | 7 | // the function is used when no update function is given 8 | function defaultUpdateFunction(instance, data) { 9 | if (instance.isSeempleArray) { 10 | instance.recreate(data); 11 | } else if (instance.isSeempleObject) { 12 | instance.setData(data, { replaceData: true }); 13 | } else { 14 | // for other objects just extend them with given data 15 | assign(instance, data); 16 | } 17 | } 18 | 19 | // returns mediator which controls assignments 20 | function createInstantiateMediator({ 21 | UsedClass, 22 | updateFunction 23 | }) { 24 | return function mediator(value, previousValue, key, object) { 25 | if (previousValue instanceof UsedClass) { 26 | updateFunction.call(object, previousValue, value, key); 27 | return previousValue; 28 | } 29 | 30 | return new UsedClass(value, object, key); 31 | }; 32 | } 33 | 34 | 35 | // creates an instance of given class as property value 36 | // and updates an instance on new value assignment instead of actual assignment 37 | export default function instantiate(object, givenKeys, UsedClass, givenUpdateFunction) { 38 | if (typeof this === 'object' && this.isSeemple) { 39 | // when context is Seemple instance, use this as an object and shift other args 40 | /* eslint-disable no-param-reassign */ 41 | givenUpdateFunction = UsedClass; 42 | UsedClass = givenKeys; 43 | givenKeys = object; 44 | object = this; 45 | /* eslint-enable no-param-reassign */ 46 | } else { 47 | // throw error when object type is wrong 48 | checkObjectType(object, 'instantiate'); 49 | } 50 | 51 | const isKeysArray = givenKeys instanceof Array; 52 | 53 | // allow to use key-class object 54 | if (typeof givenKeys === 'object' && !isKeysArray) { 55 | forOwn(givenKeys, (objVal, objKey) => instantiate(object, objKey, objVal, UsedClass)); 56 | return object; 57 | } 58 | 59 | // allow to use both single key and an array of keys 60 | const keys = isKeysArray ? givenKeys : [givenKeys]; 61 | const updateFunction = givenUpdateFunction || defaultUpdateFunction; 62 | const mediator = createInstantiateMediator({ 63 | UsedClass, 64 | updateFunction 65 | }); 66 | 67 | // iterate over all keys and define created mediator for all of them 68 | forEach(keys, (key) => mediate(object, key, mediator)); 69 | 70 | return object; 71 | } 72 | -------------------------------------------------------------------------------- /packages/seemple/src/lookforbinder.js: -------------------------------------------------------------------------------- 1 | import defaultBinders from './defaultbinders'; 2 | 3 | // tries to find a binder for given node 4 | export default function lookForBinder(node) { 5 | for (let i = 0; i < defaultBinders.length; i++) { 6 | const binder = defaultBinders[i].call(node, node); 7 | if (binder) { 8 | return binder; 9 | } 10 | } 11 | 12 | return undefined; 13 | } 14 | -------------------------------------------------------------------------------- /packages/seemple/src/mediate.js: -------------------------------------------------------------------------------- 1 | import initSeemple from './_core/init'; 2 | import defineProp from './_core/defineprop'; 3 | import checkObjectType from './_helpers/checkobjecttype'; 4 | import forEach from './_helpers/foreach'; 5 | import set from './set'; 6 | import seempleError from './_helpers/seempleerror'; 7 | import forOwn from './_helpers/forown'; 8 | 9 | // creates property mediator 10 | function createMediator({ 11 | object, 12 | propDef, 13 | key, 14 | mediator 15 | }) { 16 | return function propMediator(value) { 17 | // args: value, previousValue, key, object itself 18 | return mediator.call(object, value, propDef.value, key, object); 19 | }; 20 | } 21 | 22 | // transforms property value on its changing 23 | export default function mediate(object, givenKeys, mediator) { 24 | if (typeof this === 'object' && this.isSeemple) { 25 | // when context is Seemple instance, use this as an object and shift other args 26 | /* eslint-disable no-param-reassign */ 27 | mediator = givenKeys; 28 | givenKeys = object; 29 | object = this; 30 | /* eslint-enable no-param-reassign */ 31 | } else { 32 | // throw error when object type is wrong 33 | checkObjectType(object, 'mediate'); 34 | } 35 | 36 | const isKeysArray = givenKeys instanceof Array; 37 | 38 | // allow to use key-mediator object as another method variation 39 | if (typeof givenKeys === 'object' && !isKeysArray) { 40 | forOwn(givenKeys, (objVal, objKey) => mediate(object, objKey, objVal)); 41 | return object; 42 | } 43 | 44 | initSeemple(object); 45 | 46 | // allow to use both single key and an array of keys 47 | const keys = isKeysArray ? givenKeys : [givenKeys]; 48 | 49 | forEach(keys, (key) => { 50 | // if non-string is passed as a key 51 | if (typeof key !== 'string') { 52 | throw seempleError('mediate:key_type', { key }); 53 | } 54 | 55 | const propDef = defineProp(object, key); 56 | 57 | const propMediator = propDef.mediator = createMediator({ 58 | object, 59 | propDef, 60 | key, 61 | mediator 62 | }); 63 | 64 | // set new value 65 | set(object, key, propMediator(propDef.value), { 66 | fromMediator: true 67 | }); 68 | }); 69 | 70 | return object; 71 | } 72 | -------------------------------------------------------------------------------- /packages/seemple/src/object/_afterinit.js: -------------------------------------------------------------------------------- 1 | import afterSeempleInit from '../seemple/_afterinit'; 2 | import addListener from '../on/_addlistener'; 3 | import triggerOne from '../trigger/_triggerone'; 4 | import defs from '../_core/defs'; 5 | 6 | // called on _change:delegated 7 | // tiggers asterisk events logic by triggering _asterisk:set 8 | function changeDelegatedHandler(eventOptions = {}) { 9 | const { key } = eventOptions; 10 | const def = defs.get(this); 11 | 12 | if (key && key in def.keys) { 13 | triggerOne(this, '_asterisk:set', eventOptions); 14 | } 15 | } 16 | 17 | // called on _delete:delegated 18 | // removes asterisk events logic by triggering _asterisk:remove 19 | function deleteDelegatedHandler(eventOptions = {}) { 20 | const { key } = eventOptions; 21 | const def = defs.get(this); 22 | 23 | if (key && key in def.keys) { 24 | triggerOne(this, '_asterisk:remove', eventOptions); 25 | } 26 | } 27 | 28 | // called on change 29 | // triggers set and modify if data keys are changed 30 | function changeHandler(eventOptions = {}) { 31 | const { key, silent } = eventOptions; 32 | const def = defs.get(this); 33 | 34 | if (key && key in def.keys && !silent) { 35 | triggerOne(this, 'set', eventOptions); 36 | triggerOne(this, 'modify', eventOptions); 37 | } 38 | } 39 | 40 | // called on delete 41 | // triggers remove and modify if data keys are removed 42 | function deleteHandler(eventOptions = {}) { 43 | const { key, silent } = eventOptions; 44 | const def = defs.get(this); 45 | 46 | if (key && key in def.keys) { 47 | delete def.keys[key]; 48 | 49 | if (!silent) { 50 | triggerOne(this, 'remove', eventOptions); 51 | triggerOne(this, 'modify', eventOptions); 52 | } 53 | } 54 | } 55 | 56 | // Seemple.Object initializer 57 | export default function afterSeempleObjectInit(def) { 58 | // Seemple initializer 59 | afterSeempleInit.call(this); 60 | // create a set of data keys 61 | def.keys = {}; 62 | 63 | // trigger asterisk events 64 | addListener(this, '_change:delegated', changeDelegatedHandler); 65 | 66 | // trigger asterisk events removal 67 | addListener(this, '_delete:delegated', deleteDelegatedHandler); 68 | 69 | // fire "modify" and "set" events when data key is changed 70 | addListener(this, 'change', changeHandler); 71 | 72 | // fire "modify" and "remove" events when data key is removed 73 | addListener(this, 'delete', deleteHandler); 74 | } 75 | -------------------------------------------------------------------------------- /packages/seemple/src/object/_prototype.js: -------------------------------------------------------------------------------- 1 | import _afterInit from './_afterinit'; 2 | import addDataKeys from './adddatakeys'; 3 | import removeDataKeys from './removedatakeys'; 4 | import isDataKey from './isdatakey'; 5 | import setData from './setdata'; 6 | import keyOf from './keyof'; 7 | import keys from './keys'; 8 | import values from './values'; 9 | import entries from './entries'; 10 | import toJSON from './tojson'; 11 | import each from './each'; 12 | import iterator from './iterator'; 13 | 14 | const symbolIterator = typeof Symbol === 'function' ? Symbol.iterator : '@@iterator'; 15 | 16 | export default { 17 | _afterInit, 18 | setData, 19 | addDataKeys, 20 | removeDataKeys, 21 | isDataKey, 22 | keys, 23 | values, 24 | entries, 25 | keyOf, 26 | toJSON, 27 | each, 28 | isSeempleObject: true, 29 | jset: setData, // alias 30 | [symbolIterator]: iterator 31 | }; 32 | -------------------------------------------------------------------------------- /packages/seemple/src/object/adddatakeys.js: -------------------------------------------------------------------------------- 1 | import initSeemple from '../_core/init'; 2 | import defineProp from '../_core/defineprop'; 3 | import seempleError from '../_helpers/seempleerror'; 4 | import forEach from '../_helpers/foreach'; 5 | import triggerOne from '../trigger/_triggerone'; 6 | 7 | // adds keys to a list of data keys 8 | export default function addDataKeys(givenKeys) { 9 | const { keys } = initSeemple(this); 10 | 11 | let newKeys; 12 | 13 | // accept an array keys or a list of args 14 | if (givenKeys instanceof Array) { 15 | newKeys = givenKeys; 16 | } else { 17 | newKeys = arguments; 18 | } 19 | 20 | forEach(newKeys, (key) => { 21 | if (typeof key !== 'string') { 22 | throw seempleError('adddatakeys:key_type', { key }); 23 | } 24 | 25 | // if key is not in a list of keys 26 | if (!(key in keys)) { 27 | // define descriptors for this property 28 | const { value } = defineProp(this, key); 29 | const eventOptions = { key, value }; 30 | 31 | // add a key to the list of keys 32 | keys[key] = true; 33 | 34 | // trigger events which say that data is changed 35 | triggerOne(this, 'set', eventOptions); 36 | triggerOne(this, 'modify', eventOptions); 37 | } 38 | }); 39 | 40 | return this; 41 | } 42 | -------------------------------------------------------------------------------- /packages/seemple/src/object/each.js: -------------------------------------------------------------------------------- 1 | import defs from '../_core/defs'; 2 | import forOwn from '../_helpers/forown'; 3 | 4 | // iterates over data keys and calls callback on every iteration 5 | // @IE for..of is preferable and the method will be removed in one of major versions 6 | export default function each(callback, thisArg) { 7 | const def = defs.get(this); 8 | const ctx = typeof thisArg !== 'undefined' ? thisArg : this; 9 | 10 | /* istanbul ignore if */ 11 | if (!def) { 12 | return this; 13 | } 14 | 15 | forOwn(def.keys, (_, key) => { 16 | callback.call(ctx, this[key], key, this); 17 | }); 18 | 19 | return this; 20 | } 21 | -------------------------------------------------------------------------------- /packages/seemple/src/object/entries.js: -------------------------------------------------------------------------------- 1 | import defs from '../_core/defs'; 2 | 3 | // returns an array which contains things like [key, value] 4 | export default function keys() { 5 | const def = defs.get(this); 6 | 7 | /* istanbul ignore if */ 8 | if (!def) { 9 | return []; 10 | } 11 | 12 | const keysArr = Object.keys(def.keys); 13 | const { length } = keysArr; 14 | const result = new Array(length); 15 | 16 | for (let i = 0; i < keysArr.length; i++) { 17 | const key = keysArr[i]; 18 | result[i] = [key, this[key]]; 19 | } 20 | 21 | return result; 22 | } 23 | -------------------------------------------------------------------------------- /packages/seemple/src/object/index.js: -------------------------------------------------------------------------------- 1 | import Class from '../class'; 2 | import Seemple from '../seemple'; 3 | import instanceMembers from './_prototype'; 4 | import seempleError from '../_helpers/seempleerror'; 5 | import initSeemple from '../_core/init'; 6 | 7 | instanceMembers.extends = Seemple; 8 | 9 | instanceMembers.constructor = function SeempleObject(data) { 10 | if (!(this instanceof SeempleObject)) { 11 | throw seempleError('common:call_class'); 12 | } 13 | 14 | initSeemple(this); 15 | 16 | // return is used to make possible to chain super() calls 17 | return typeof data !== 'undefined' ? this.setData(data) : this; 18 | }; 19 | 20 | const SeempleObject = Class(instanceMembers); 21 | 22 | export default SeempleObject; 23 | -------------------------------------------------------------------------------- /packages/seemple/src/object/isdatakey.js: -------------------------------------------------------------------------------- 1 | import defs from '../_core/defs'; 2 | 3 | // checks is a key present in data keys list 4 | export default function isDataKey(key) { 5 | const def = defs.get(this); 6 | 7 | /* istanbul ignore if */ 8 | if (!def) { 9 | return false; 10 | } 11 | 12 | return key in def.keys; 13 | } 14 | -------------------------------------------------------------------------------- /packages/seemple/src/object/iterator.js: -------------------------------------------------------------------------------- 1 | // Symbol.iterator of Seemple.Object instances 2 | export default function seempleObjectIterator() { 3 | const keys = this.keys(); 4 | let i = 0; 5 | 6 | return { 7 | next: () => { 8 | if (i > keys.length - 1) { 9 | return { done: true }; 10 | } 11 | 12 | return { 13 | done: false, 14 | value: this[keys[i++]] // eslint-disable-line no-plusplus 15 | }; 16 | } 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /packages/seemple/src/object/keyof.js: -------------------------------------------------------------------------------- 1 | import defs from '../_core/defs'; 2 | 3 | // iterates over data keys looking for a property with given value 4 | // and returns a key of found property 5 | export default function keyOf(value) { 6 | const def = defs.get(this); 7 | 8 | /* istanbul ignore if */ 9 | if (!def) { 10 | return null; 11 | } 12 | 13 | const keysArray = Object.keys(def.keys); 14 | 15 | for (let i = 0; i < keysArray.length; i++) { 16 | const key = keysArray[i]; 17 | if (this[key] === value) { 18 | return key; 19 | } 20 | } 21 | 22 | return null; 23 | } 24 | -------------------------------------------------------------------------------- /packages/seemple/src/object/keys.js: -------------------------------------------------------------------------------- 1 | import defs from '../_core/defs'; 2 | 3 | // returns an array which contains all data keys 4 | export default function keys() { 5 | const def = defs.get(this); 6 | 7 | /* istanbul ignore if */ 8 | if (!def) { 9 | return []; 10 | } 11 | 12 | return Object.keys(def.keys); 13 | } 14 | -------------------------------------------------------------------------------- /packages/seemple/src/object/removedatakeys.js: -------------------------------------------------------------------------------- 1 | import defs from '../_core/defs'; 2 | import triggerOne from '../trigger/_triggerone'; 3 | import seempleError from '../_helpers/seempleerror'; 4 | import forEach from '../_helpers/foreach'; 5 | 6 | // removes given keys from a list of data keys 7 | export default function removeDataKeys(givenKeys) { 8 | const def = defs.get(this); 9 | 10 | /* istanbul ignore if */ 11 | if (!def) { 12 | return this; 13 | } 14 | 15 | const { keys } = def; 16 | let removedKeys; 17 | 18 | // accept an array keys or a list of args 19 | if (givenKeys instanceof Array) { 20 | removedKeys = givenKeys; 21 | } else { 22 | removedKeys = arguments; 23 | } 24 | 25 | forEach(removedKeys, (key) => { 26 | if (typeof key !== 'string') { 27 | throw seempleError('removedatakeys:key_type', { key }); 28 | } 29 | 30 | if (key in keys) { 31 | const eventOptions = { 32 | key, 33 | value: this[key] 34 | }; 35 | 36 | delete keys[key]; 37 | 38 | // fire "modify" and "remove" events 39 | triggerOne(this, 'modify', eventOptions); 40 | triggerOne(this, 'remove', eventOptions); 41 | } 42 | }); 43 | 44 | return this; 45 | } 46 | -------------------------------------------------------------------------------- /packages/seemple/src/object/setdata.js: -------------------------------------------------------------------------------- 1 | import initSeemple from '../_core/init'; 2 | import defineProp from '../_core/defineprop'; 3 | import forOwn from '../_helpers/forown'; 4 | import set from '../set'; 5 | 6 | // returns an array of keys listed at inObject but not listed at fromObject 7 | function getNotListedKeys(inObject, fromObject) { 8 | const result = []; 9 | forOwn(inObject, (_, key) => { 10 | if (!(key in fromObject)) { 11 | result.push(key); 12 | } 13 | }); 14 | 15 | return result; 16 | } 17 | 18 | // changes property value and adds given key to a list of data keys 19 | export default function setData(key, value, eventOptions) { 20 | // if no key or falsy key is given 21 | if (!key) { 22 | return this; 23 | } 24 | 25 | const { keys } = initSeemple(this); 26 | 27 | // allow to pass key-value object 28 | if (typeof key === 'object') { 29 | eventOptions = value || {}; // eslint-disable-line no-param-reassign 30 | 31 | const { replaceData } = eventOptions; 32 | 33 | // do not call setData recursivally for better performance 34 | forOwn(key, (objVal, objKey) => { 35 | // remove data keys not listed at key-value object 36 | if (replaceData) { 37 | const notListedKeys = getNotListedKeys(keys, key); 38 | 39 | if (notListedKeys.length) { 40 | this.removeDataKeys(notListedKeys); 41 | } 42 | } 43 | 44 | // define descriptors for given property 45 | defineProp(this, objKey); 46 | 47 | // add a key to a list of keys 48 | keys[objKey] = 1; 49 | 50 | // do other things with set method 51 | set(this, objKey, objVal, eventOptions); 52 | }); 53 | 54 | return this; 55 | } 56 | 57 | eventOptions = eventOptions || {}; // eslint-disable-line no-param-reassign 58 | 59 | const { replaceData } = eventOptions; 60 | 61 | // remove all data keys except given key 62 | if (replaceData) { 63 | const notListedKeys = getNotListedKeys(keys, { [key]: true }); 64 | 65 | if (notListedKeys.length) { 66 | this.removeDataKeys(notListedKeys); 67 | } 68 | } 69 | 70 | // define descriptors for given property 71 | defineProp(this, key); 72 | 73 | // add a key to a list of keys 74 | keys[key] = 1; 75 | 76 | // do other things with set method 77 | return set(this, key, value, eventOptions); 78 | } 79 | -------------------------------------------------------------------------------- /packages/seemple/src/object/tojson.js: -------------------------------------------------------------------------------- 1 | import initSeemple from '../_core/init'; 2 | import forOwn from '../_helpers/forown'; 3 | 4 | // converts Seemple.Object instance to ordinary object 5 | export default function toJSON(recursive = true) { 6 | const { keys } = initSeemple(this); 7 | const result = {}; 8 | 9 | forOwn(keys, (_, key) => { 10 | const value = this[key]; 11 | // when recursive is true and when value has toJSON method then call it recusively 12 | if (recursive && value && typeof value.toJSON === 'function') { 13 | result[key] = value.toJSON(true); 14 | } else { 15 | result[key] = value; 16 | } 17 | }); 18 | 19 | return result; 20 | } 21 | -------------------------------------------------------------------------------- /packages/seemple/src/object/values.js: -------------------------------------------------------------------------------- 1 | import defs from '../_core/defs'; 2 | 3 | // returns an array which contains all data values 4 | export default function keys() { 5 | const def = defs.get(this); 6 | 7 | /* istanbul ignore if */ 8 | if (!def) { 9 | return []; 10 | } 11 | 12 | const keysArr = Object.keys(def.keys); 13 | const { length } = keysArr; 14 | const result = new Array(length); 15 | 16 | for (let i = 0; i < keysArr.length; i++) { 17 | result[i] = this[keysArr[i]]; 18 | } 19 | 20 | return result; 21 | } 22 | -------------------------------------------------------------------------------- /packages/seemple/src/off/_removedomlistener.js: -------------------------------------------------------------------------------- 1 | import defs from '../_core/defs'; 2 | import removeListener from './_removelistener'; 3 | import dom from '../_dom'; 4 | import forEach from '../_helpers/foreach'; 5 | 6 | // removes dom listener from nodes bound to given key 7 | export default function removeDomListener( 8 | object, 9 | key, 10 | eventName, 11 | selector, 12 | callback, 13 | context, 14 | info 15 | ) { 16 | const def = defs.get(object); 17 | 18 | if (!def) { 19 | return object; 20 | } 21 | 22 | const { props } = def; 23 | const propDef = props[key]; 24 | 25 | if (!propDef) { 26 | return object; 27 | } 28 | 29 | const { bindings } = propDef; 30 | 31 | if (bindings) { 32 | // collect bound nodes and remove DOM event listener 33 | const nodes = Array(bindings.length); 34 | const eventNamespace = def.id + key; 35 | 36 | forEach(bindings, (binding, index) => { 37 | nodes[index] = binding.node; 38 | }); 39 | 40 | dom.$(nodes).off(`${eventName}.${eventNamespace}`, selector, callback); 41 | } 42 | 43 | // remove bind and unbind listeners from given key 44 | removeListener(object, `bind:${key}`, callback, context, info); 45 | removeListener(object, `unbind:${key}`, callback, context, info); 46 | 47 | return object; 48 | } 49 | -------------------------------------------------------------------------------- /packages/seemple/src/off/_removetreelistener.js: -------------------------------------------------------------------------------- 1 | import undelegateListener from './_undelegatelistener'; 2 | 3 | // removes tree listener from all object tree of fiven path 4 | // TODO: Pass context to removeTreeListener 5 | export default function removeTreeListener(object, deepPath, handler) { 6 | if (typeof deepPath === 'string') { 7 | deepPath = deepPath.split('.'); // eslint-disable-line no-param-reassign 8 | } 9 | 10 | // iterate over keys of the path and undelegate given handler (can be undefined) 11 | for (let i = 0; i < deepPath.length; i++) { 12 | // TODO: Array.prototype.slice is slow 13 | const listenedPath = deepPath.slice(0, i); 14 | 15 | undelegateListener( 16 | object, 17 | listenedPath, 18 | `_change:tree:${deepPath[i]}`, 19 | handler 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/seemple/src/off/index.js: -------------------------------------------------------------------------------- 1 | import splitBySpaceReg from '../on/_splitbyspaceregexp'; 2 | import checkObjectType from '../_helpers/checkobjecttype'; 3 | import forOwn from '../_helpers/forown'; 4 | import forEach from '../_helpers/foreach'; 5 | import defs from '../_core/defs'; 6 | import removeListener from './_removelistener'; 7 | import undelegateListener from './_undelegatelistener'; 8 | import dom from '../_dom'; 9 | 10 | // removes event listener 11 | export default function off(object, givenNames, callback, context) { 12 | if (typeof this === 'object' && this.isSeemple) { 13 | // when context is Seemple instance, use this as an object and shift other args 14 | /* eslint-disable no-param-reassign */ 15 | context = callback; 16 | callback = givenNames; 17 | givenNames = object; 18 | object = this; 19 | /* eslint-enable no-param-reassign */ 20 | } else { 21 | // throw error when object type is wrong 22 | checkObjectType(object, 'off'); 23 | } 24 | 25 | const isNamesVarArray = givenNames instanceof Array; 26 | const def = defs.get(object); 27 | 28 | // allow to pass name-handler object 29 | // TODO: Name-handler object passed to off method is non-documented feature 30 | if (givenNames && typeof givenNames === 'object' && !isNamesVarArray) { 31 | forOwn(givenNames, (namesObjCallback, namesObjName) => off( 32 | object, namesObjName, namesObjCallback, callback 33 | )); 34 | return object; 35 | } 36 | 37 | 38 | if (!givenNames && !callback && !context) { 39 | def.events = {}; 40 | 41 | forOwn(def.props, ({ bindings }, propName) => { 42 | if (bindings) { 43 | forEach(bindings, ({ node }) => { 44 | const eventNamespace = def.id + propName; 45 | dom.$(node).off(`.${eventNamespace}`); 46 | }); 47 | } 48 | }); 49 | 50 | return object; 51 | } 52 | 53 | // TODO: Array of names passed to off method is non-documented feature 54 | // split by spaces 55 | const names = isNamesVarArray ? givenNames : givenNames.split(splitBySpaceReg); 56 | 57 | forEach(names, (name) => { 58 | const delegatedEventParts = name.split('@'); 59 | if (delegatedEventParts.length > 1) { 60 | const [path, delegatedName] = delegatedEventParts; 61 | undelegateListener(object, path, delegatedName, callback, context); 62 | } else { 63 | removeListener(object, name, callback, context); 64 | } 65 | }); 66 | 67 | return object; 68 | } 69 | -------------------------------------------------------------------------------- /packages/seemple/src/on/_adddomlistener.js: -------------------------------------------------------------------------------- 1 | import initSeemple from '../_core/init'; 2 | import defineProp from '../_core/defineprop'; 3 | import addListener from './_addlistener'; 4 | import dom from '../_dom'; 5 | import createDomEventHandler from './_createdomeventhandler'; 6 | import forEach from '../_helpers/foreach'; 7 | 8 | // returns an object with event handlers used at addDomListener 9 | function createBindingHandlers({ 10 | fullEventName, 11 | domEventHandler, 12 | selector 13 | }) { 14 | return { 15 | bindHandler(evt = {}) { 16 | const { node } = evt; 17 | if (node) { 18 | dom.$(node).on(fullEventName, selector, domEventHandler); 19 | } 20 | }, 21 | unbindHandler(evt = {}) { 22 | const { node } = evt; 23 | if (node) { 24 | dom.$(node).off(fullEventName, selector, domEventHandler); 25 | } 26 | } 27 | }; 28 | } 29 | 30 | // adds DOM event listener for nodes bound to given property 31 | export default function addDomListener(object, key, eventName, selector, callback, context, info) { 32 | const def = initSeemple(object); 33 | const propDef = defineProp(object, key); 34 | 35 | const domEventHandler = createDomEventHandler({ 36 | key, 37 | object, 38 | callback, 39 | context: context || object 40 | }); 41 | 42 | // making possible to remove this event listener 43 | domEventHandler._callback = callback; 44 | 45 | const eventNamespace = def.id + key; 46 | const fullEventName = `${eventName}.${eventNamespace}`; 47 | const { bindHandler, unbindHandler } = createBindingHandlers({ 48 | fullEventName, 49 | domEventHandler, 50 | selector 51 | }); 52 | const addBindListenerResult = addListener(object, `bind:${key}`, bindHandler, context, info); 53 | const addUnbindListenerResult = addListener(object, `unbind:${key}`, unbindHandler, context, info); 54 | 55 | // if events are added successfully then run bindHandler for every node immediately 56 | // TODO: Describe why do we need addBindListenerResult and addUnbindListenerResult 57 | if (addBindListenerResult && addUnbindListenerResult) { 58 | const { bindings } = propDef; 59 | if (bindings) { 60 | forEach(bindings, ({ node }) => bindHandler({ node })); 61 | } 62 | } 63 | 64 | return object; 65 | } 66 | -------------------------------------------------------------------------------- /packages/seemple/src/on/_addtreelistener.js: -------------------------------------------------------------------------------- 1 | import delegateListener from './_delegatelistener'; 2 | import removeTreeListener from '../off/_removetreelistener'; 3 | 4 | // creates tree listener 5 | function createTreeListener({ handler, restPath }) { 6 | const newHandler = function treeListener(changeEvent) { 7 | const extendedChangeEvent = { 8 | restPath, 9 | ...changeEvent 10 | }; 11 | const { previousValue, value } = changeEvent; 12 | 13 | // removes listener for all branches of the path on old object 14 | if (previousValue && typeof previousValue === 'object') { 15 | removeTreeListener(previousValue, restPath, handler); 16 | } 17 | 18 | // adds listener for all branches of "restPath" path on newly assigned object 19 | if (value && typeof value === 'object') { 20 | addTreeListener(value, restPath, handler); 21 | } 22 | 23 | // call original handler 24 | handler.call(this, extendedChangeEvent); 25 | }; 26 | 27 | newHandler._callback = handler; 28 | 29 | return newHandler; 30 | } 31 | 32 | // listens changes for all branches of given path 33 | // TODO: Pass context to addTreeListener 34 | // one of the most hard functions to understand 35 | export default function addTreeListener(object, deepPath, handler) { 36 | if (typeof deepPath === 'string') { 37 | deepPath = deepPath.split('.'); // eslint-disable-line no-param-reassign 38 | } 39 | 40 | // iterate over all keys and delegate listener for all objects of given branch 41 | for (let i = 0; i < deepPath.length; i++) { 42 | // TODO: Array.prototype.slice method is slow 43 | const listenPath = deepPath.slice(0, i); 44 | const restPath = deepPath.slice(i + 1); 45 | 46 | delegateListener( 47 | object, 48 | listenPath, 49 | `_change:tree:${deepPath[i]}`, 50 | createTreeListener({ 51 | handler, 52 | restPath 53 | }) 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/seemple/src/on/_createdomeventhandler.js: -------------------------------------------------------------------------------- 1 | import apply from '../_helpers/apply'; 2 | // returns DOM event handler 3 | export default function createDomEventHandler({ 4 | key, 5 | object, 6 | callback, 7 | context 8 | }) { 9 | return function domEventHandler(domEvent) { 10 | const originalEvent = domEvent.originalEvent || domEvent; 11 | // seempleTriggerArgs are created when DOM event is triggered by trigger method 12 | const triggerArgs = originalEvent.seempleTriggerArgs; 13 | const { 14 | which, target, ctrlKey, altKey 15 | } = domEvent; 16 | 17 | if (triggerArgs) { 18 | // if args are passed to trigger method then pass them to an event handler 19 | apply(callback, context, triggerArgs); 20 | } else { 21 | // use the following object as an arg for event handler 22 | callback.call(context, { 23 | self: object, 24 | node: this, 25 | preventDefault: () => domEvent.preventDefault(), 26 | stopPropagation: () => domEvent.stopPropagation(), 27 | key, 28 | domEvent, 29 | originalEvent, 30 | which, 31 | target, 32 | ctrlKey, 33 | altKey 34 | }); 35 | } 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /packages/seemple/src/on/_delegatelistener/arrayaddhandler.js: -------------------------------------------------------------------------------- 1 | import triggerOne from '../../trigger/_triggerone'; 2 | import forEach from '../../_helpers/foreach'; 3 | 4 | 5 | // the function is called when something is added to an array 6 | // it delegates asterisk listener for newly added items 7 | export default function arrayAddHandler({ added }, { 8 | path, 9 | name, 10 | callback, 11 | context, 12 | info 13 | } = triggerOne.latestEvent.info.delegatedData) { 14 | forEach(added, (item) => { 15 | if (item && typeof item === 'object') { 16 | const delegateListener = require('./').default; // fixing circular ref 17 | 18 | delegateListener(item, path, name, callback, context, info); 19 | } 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /packages/seemple/src/on/_delegatelistener/arrayremovehandler.js: -------------------------------------------------------------------------------- 1 | import undelegateListener from '../../off/_undelegatelistener'; 2 | import triggerOne from '../../trigger/_triggerone'; 3 | import forEach from '../../_helpers/foreach'; 4 | 5 | 6 | // the function is called when something is removed from an array 7 | // it undelegates asterisk listener from removed items 8 | export default function arrayRemoveHandler({ removed }, { 9 | path, 10 | name, 11 | callback, 12 | context, 13 | info 14 | } = triggerOne.latestEvent.info.delegatedData) { 15 | if (removed && removed.length) { 16 | forEach(removed, (item) => { 17 | if (item && typeof item === 'object') { 18 | undelegateListener(item, path, name, callback, context, info); 19 | } 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/seemple/src/on/_delegatelistener/changehandler.js: -------------------------------------------------------------------------------- 1 | import undelegateListener from '../../off/_undelegatelistener'; 2 | import triggerOne from '../../trigger/_triggerone'; 3 | 4 | // the function is called when some part of a path is changed 5 | // it delegates event listener for new branch of an object and undelegates it for old one 6 | // used for non-asterisk events 7 | export default function changeHandler({ 8 | previousValue, 9 | value 10 | }, { 11 | path, 12 | name, 13 | callback, 14 | context, 15 | info 16 | } = triggerOne.latestEvent.info.delegatedData) { 17 | if (value && typeof value === 'object') { 18 | const delegateListener = require('./').default; // fixing circular ref 19 | 20 | delegateListener(value, path, name, callback, context, info); 21 | } 22 | 23 | if (previousValue && typeof previousValue === 'object') { 24 | undelegateListener(previousValue, path, name, callback, context, info); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/seemple/src/on/_delegatelistener/objectremovehandler.js: -------------------------------------------------------------------------------- 1 | import undelegateListener from '../../off/_undelegatelistener'; 2 | import triggerOne from '../../trigger/_triggerone'; 3 | 4 | // the function is called when data property is removed from Seemple.Object 5 | // it undelegates asterisk listener from removed object 6 | export default function objectRemoveHandler({ value: item }, { 7 | path, 8 | name, 9 | callback, 10 | context, 11 | info 12 | // , object 13 | } = triggerOne.latestEvent.info.delegatedData) { 14 | if (item && typeof item === 'object') { 15 | undelegateListener(item, path, name, callback, context, info); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/seemple/src/on/_delegatelistener/objectsethandler.js: -------------------------------------------------------------------------------- 1 | import triggerOne from '../../trigger/_triggerone'; 2 | import defs from '../../_core/defs'; 3 | 4 | // the function is called when data property is changed in Seemple.Object 5 | // it delegates asterisk listener for new value 6 | export default function objectSetHandler({ key }, { 7 | path, 8 | name, 9 | callback, 10 | context, 11 | info, 12 | object 13 | } = triggerOne.latestEvent.info.delegatedData) { 14 | if (key) { 15 | const item = object[key]; 16 | 17 | if (item && typeof item === 'object') { 18 | const def = defs.get(object); 19 | if (key in def.keys) { 20 | const delegateListener = require('./').default; // fixing circular ref 21 | 22 | delegateListener(item, path, name, callback, context, info); 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/seemple/src/on/_domeventregexp.js: -------------------------------------------------------------------------------- 1 | // the regexp allows to parse things like "click::x(.y)" 2 | // it's shared between few modules 3 | export default /([^::]+)::([^()]+)?(?:\((.*)\))?/; 4 | -------------------------------------------------------------------------------- /packages/seemple/src/on/_splitbyspaceregexp.js: -------------------------------------------------------------------------------- 1 | // allows to split by spaces not inclusing ones inside of brackers 2 | export default /\s+(?![^(]*\))/g; 3 | -------------------------------------------------------------------------------- /packages/seemple/src/once.js: -------------------------------------------------------------------------------- 1 | import on from './on'; 2 | import checkObjectType from './_helpers/checkobjecttype'; 3 | import forOwn from './_helpers/forown'; 4 | import off from './off'; 5 | import apply from './_helpers/apply'; 6 | 7 | // adds event listener which will be removed immediately after its first call 8 | export default function once(object, names, givenCallback, context) { 9 | if (typeof this === 'object' && this.isSeemple) { 10 | // when context is Seemple instance, use this as an object and shift other args 11 | /* eslint-disable no-param-reassign */ 12 | context = givenCallback; 13 | givenCallback = names; 14 | names = object; 15 | object = this; 16 | /* eslint-enable no-param-reassign */ 17 | } else { 18 | // throw error when object type is wrong 19 | checkObjectType(object, 'once'); 20 | } 21 | 22 | const isNamesVarArray = names instanceof Array; 23 | 24 | // allow to pass name-handler object 25 | if (names && typeof names === 'object' && !isNamesVarArray) { 26 | forOwn(names, (namesObjCallback, namesObjName) => once( 27 | object, namesObjName, namesObjCallback, givenCallback 28 | )); 29 | return object; 30 | } 31 | 32 | const callback = function onceCallback() { 33 | apply(givenCallback, this, arguments); 34 | // remove event listener after its call 35 | off(object, names, onceCallback, context); 36 | }; 37 | 38 | // allow to remove event listener py passing original callback to "off" 39 | callback._callback = givenCallback; 40 | 41 | return on(object, names, callback, context); 42 | } 43 | -------------------------------------------------------------------------------- /packages/seemple/src/ondebounce.js: -------------------------------------------------------------------------------- 1 | import on from './on'; 2 | import checkObjectType from './_helpers/checkobjecttype'; 3 | import debounce from './_helpers/debounce'; 4 | import forOwn from './_helpers/forown'; 5 | 6 | // adds debounced event listener 7 | export default function onDebounce( 8 | object, 9 | names, 10 | givenCallback, 11 | givenDelay, 12 | triggerOnInit, 13 | context 14 | ) { 15 | if (typeof this === 'object' && this.isSeemple) { 16 | // when context is Seemple instance, use this as an object and shift other args 17 | /* eslint-disable no-param-reassign */ 18 | context = triggerOnInit; 19 | triggerOnInit = givenDelay; 20 | givenDelay = givenCallback; 21 | givenCallback = names; 22 | names = object; 23 | object = this; 24 | /* eslint-enable no-param-reassign */ 25 | } else { 26 | // throw error when object type is wrong 27 | checkObjectType(object, 'onDebounce'); 28 | } 29 | 30 | const isNamesVarArray = names instanceof Array; 31 | 32 | // allow to pass name-handler object 33 | if (names && typeof names === 'object' && !isNamesVarArray) { 34 | forOwn(names, (namesObjCallback, namesObjName) => onDebounce( 35 | object, 36 | namesObjName, 37 | namesObjCallback, 38 | givenCallback, 39 | givenDelay, 40 | triggerOnInit 41 | )); 42 | 43 | return object; 44 | } 45 | 46 | const delay = typeof givenDelay === 'number' ? givenDelay : 0; 47 | 48 | const callback = debounce(givenCallback, delay); 49 | 50 | // allow to remove event listener py passing original callback to "off" 51 | callback._callback = givenCallback; 52 | 53 | return on(object, names, callback, triggerOnInit, context); 54 | } 55 | -------------------------------------------------------------------------------- /packages/seemple/src/parsebindings/_parserdata.js: -------------------------------------------------------------------------------- 1 | import calc from '../calc'; 2 | import parserBrackets from '../parserbrackets'; 3 | 4 | const parserData = {}; 5 | 6 | // since Seemple allows to change parser brackets via parserBrackets objects 7 | // the parser needs to generate required regular expressions and escaped brackets every time 8 | // when parseBindings is called 9 | // to optimize this behavior parserData object is created 10 | // it calculates needed data every time when parserBrackets are changed 11 | // and when parseBindings function is called it uses previously generated regeps 12 | // from parserData object 13 | calc(parserData, { 14 | leftBracket: { 15 | source: { 16 | object: parserBrackets, 17 | key: 'left' 18 | } 19 | }, 20 | rightBracket: { 21 | source: { 22 | object: parserBrackets, 23 | key: 'right' 24 | } 25 | }, 26 | escLeftBracket: { 27 | source: 'leftBracket', 28 | handler: (left) => left.replace(/(\[|\(|\?)/g, '\\$1') 29 | }, 30 | escRightBracket: { 31 | source: 'rightBracket', 32 | handler: (right) => right.replace(/(]|\)|\?)/g, '\\$1') 33 | }, 34 | bindingReg: { 35 | source: ['escLeftBracket', 'escRightBracket'], 36 | handler: (left, right) => new RegExp(`${left}\\s*(.+?)\\s*${right}`, 'g') 37 | }, 38 | strictBindingReg: { 39 | source: ['escLeftBracket', 'escRightBracket'], 40 | handler: (left, right) => new RegExp(`^${left}\\s*(.+?)\\s*${right}$`, 'g') 41 | } 42 | }, { 43 | debounceCalc: false // we need to get new regexps immediately when brackets are changed 44 | }); 45 | 46 | export default parserData; 47 | -------------------------------------------------------------------------------- /packages/seemple/src/parsebindings/_processattribute/_definehiddencontentproperty.js: -------------------------------------------------------------------------------- 1 | import calc from '../../calc'; 2 | import parserData from '../_parserdata'; 3 | 4 | const hiddenPropertyPrefix = `${Math.random()}`.replace('0.', 'hidden'); 5 | let hiddenPropertyIndex = 0; 6 | 7 | // defines hiden (without accessors) computed property 8 | // that dependent on given properties ('keys') as text template describes 9 | // for example if text='{{x}} blah {{y}}', x='foo', y='bar' 10 | // then the new property should have value 'foo blah bar' 11 | export default function defineHiddenContentProperty({ 12 | object, 13 | keys, 14 | text 15 | }) { 16 | const key = `${hiddenPropertyPrefix}${hiddenPropertyIndex}`; 17 | const regs = {}; 18 | const { escLeftBracket, escRightBracket } = parserData; 19 | 20 | hiddenPropertyIndex += 1; 21 | 22 | // create and cache regular expressions which will help us to 23 | // change target property value quickly when sources are changed 24 | // TODO: We need better parser! 25 | for (let i = 0; i < keys.length; i++) { 26 | regs[keys[i]] = new RegExp(`${escLeftBracket}\\s*${keys[i]}\\s*${escRightBracket}`, 'g'); 27 | } 28 | 29 | calc(object, key, keys, function calcHandler() { 30 | let value = text; 31 | 32 | // replace things like {{x}} by actual values 33 | for (let i = 0; i < keys.length; i++) { 34 | value = value.replace(regs[keys[i]], arguments[i]); 35 | } 36 | 37 | return value; 38 | }, { 39 | isTargetPropertyHidden: true, 40 | debounceCalc: false 41 | }); 42 | 43 | return key; 44 | } 45 | -------------------------------------------------------------------------------- /packages/seemple/src/parsebindings/_processattribute/_getbindingkey.js: -------------------------------------------------------------------------------- 1 | import parserData from '../_parserdata'; 2 | import defineHiddenContentProperty from './_definehiddencontentproperty'; 3 | 4 | // analyzes string and returns only one key which will be actually bound to an attribute 5 | export default function getBindingKey({ 6 | object, 7 | text // for example 'Hello, {{x}}' 8 | }) { 9 | const { strictBindingReg, bindingReg } = parserData; 10 | const keys = []; 11 | 12 | let execResult; 13 | let key; 14 | 15 | strictBindingReg.lastIndex = 0; 16 | bindingReg.lastIndex = 0; 17 | 18 | // extract keys given in parser brackers 19 | // '{{x}} {{y}}' -> ['x', 'y'] 20 | while ((execResult = bindingReg.exec(text))) { 21 | keys.push(execResult[1]); 22 | } 23 | 24 | if (keys.length === 1 && strictBindingReg.test(text)) { 25 | // if there is only one key and if only binding substring is present in a text 26 | // in other words '{{x}}' is given instead of '{{x}} {{y}}' or '{{x}}foo' 27 | // then don't create computable property and use that key (eg 'x') for binding 28 | key = keys[0]; 29 | } else { 30 | // create hidden computable property 31 | key = defineHiddenContentProperty({ 32 | object, 33 | keys, 34 | text 35 | }); 36 | } 37 | 38 | return key; 39 | } 40 | -------------------------------------------------------------------------------- /packages/seemple/src/parsebindings/_processattribute/index.js: -------------------------------------------------------------------------------- 1 | import getBindingKey from './_getbindingkey'; 2 | import bindNode from '../../bindnode'; 3 | import lookForBinder from '../../lookforbinder'; 4 | 5 | // a binder for instance of Attr 6 | const attributeBinder = { 7 | setValue(value) { 8 | this.value = value; 9 | } 10 | }; 11 | 12 | // adds binding for an attribute 13 | // its logic is much harder than for text node 14 | // check out imported modules for more info 15 | export default function processAttribute({ 16 | node, 17 | attribute, 18 | object, 19 | eventOptions 20 | }) { 21 | const { name, value } = attribute; 22 | const { type } = node; 23 | // get a key which will be actually bound to an attribute 24 | // getBindingKey analyzes given value, creates computable property and returns its key 25 | const key = getBindingKey({ 26 | object, 27 | text: value 28 | }); 29 | const probablyValueInput = name === 'value' && type !== 'checkbox' && type !== 'radio'; 30 | const probablyCheckableInput = name === 'checked' && (type === 'checkbox' || type === 'radio'); 31 | 32 | let defaultBinder; 33 | 34 | if (probablyValueInput || probablyCheckableInput) { 35 | defaultBinder = lookForBinder(node); 36 | } 37 | 38 | if (defaultBinder) { 39 | // if deault binder is found then this is default HTML5 form element 40 | // remove the attribute and use found binder 41 | node.removeAttribute(name); 42 | bindNode(object, key, node, defaultBinder, eventOptions); 43 | } else { 44 | // simply bind an attribute 45 | bindNode(object, key, attribute, attributeBinder, eventOptions); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/seemple/src/parsebindings/_processtextnode.js: -------------------------------------------------------------------------------- 1 | import parserData from './_parserdata'; 2 | import bindNode from '../bindnode'; 3 | import forEach from '../_helpers/foreach'; 4 | 5 | 6 | const textNodeBinder = { 7 | setValue(value) { 8 | this.textContent = typeof value === 'undefined' ? '' : value; 9 | } 10 | }; 11 | 12 | // adds binding for text node 13 | // it splits up one text node into "simple text nodes" 14 | // and "bound text nodes" and removes original text node 15 | export default function processTextNode({ 16 | object, 17 | node, 18 | textNode, 19 | eventOptions 20 | }) { 21 | const { bindingReg } = parserData; 22 | const { textContent } = textNode; 23 | const { document } = window; 24 | 25 | bindingReg.lastIndex = 0; 26 | 27 | // tokens variable contains normal text as odd items 28 | // and bound keys as even items 29 | // 'foo{{x}}bar{{y}}baz{{z}}' -> ['foo', 'x', 'bar', 'y', 'baz', 'z', ''] 30 | const tokens = textContent.split(bindingReg); 31 | 32 | // fragment contains all new text nodes 33 | const fragment = document.createDocumentFragment(); 34 | 35 | forEach(tokens, (token, index) => { 36 | if (token) { 37 | const newTextNode = document.createTextNode(token); 38 | fragment.appendChild(newTextNode); 39 | 40 | // if tokens item is even then it is a key 41 | // which needs to be bound to newly created text node 42 | if (index % 2 !== 0) { 43 | bindNode(object, token, newTextNode, textNodeBinder, eventOptions); 44 | } 45 | } 46 | }); 47 | 48 | node.insertBefore(fragment, textNode); 49 | node.removeChild(textNode); 50 | } 51 | -------------------------------------------------------------------------------- /packages/seemple/src/parserbrackets.js: -------------------------------------------------------------------------------- 1 | // brackets for bindings parser 2 | export default { 3 | left: '{{', 4 | right: '}}' 5 | }; 6 | -------------------------------------------------------------------------------- /packages/seemple/src/seemple/_afterinit.js: -------------------------------------------------------------------------------- 1 | // Seemple initializer 2 | export default function afterSeempleInit() { 3 | this.nodes = {}; 4 | this.$nodes = {}; 5 | } 6 | -------------------------------------------------------------------------------- /packages/seemple/src/seemple/_prototype.js: -------------------------------------------------------------------------------- 1 | import * as universalMethods from './_universalmethods'; 2 | import assign from '../_helpers/assign'; 3 | import _afterInit from './_afterinit'; 4 | 5 | export default assign({ 6 | _afterInit, 7 | isSeemple: true, 8 | $: universalMethods.selectAll 9 | }, universalMethods); 10 | -------------------------------------------------------------------------------- /packages/seemple/src/seemple/_staticmembers.js: -------------------------------------------------------------------------------- 1 | import defaultBinders from '../defaultbinders'; 2 | import lookForBinder from '../lookforbinder'; 3 | import parserBrackers from '../parserbrackets'; 4 | import Class from '../class'; 5 | import toSeemple from '../toseemple'; 6 | import * as binders from '../binders'; 7 | import * as universalMethods from './_universalmethods'; 8 | import assign from '../_helpers/assign'; 9 | import useDOMLibrary from '../usedomlibrary'; 10 | import chain from '../chain'; 11 | 12 | export default assign({ 13 | Class, 14 | defaultBinders, 15 | lookForBinder, 16 | binders, 17 | parserBrackers, 18 | toSeemple, 19 | useDOMLibrary, 20 | chain 21 | }, universalMethods); 22 | -------------------------------------------------------------------------------- /packages/seemple/src/seemple/_universalmethods.js: -------------------------------------------------------------------------------- 1 | import on from '../on'; 2 | import once from '../once'; 3 | import onDebounce from '../ondebounce'; 4 | import off from '../off'; 5 | import trigger from '../trigger'; 6 | import calc from '../calc'; 7 | import bindNode from '../bindnode'; 8 | import unbindNode from '../unbindnode'; 9 | import bindOptionalNode from '../bindoptionalnode'; 10 | import bindSandbox from '../bindsandbox'; 11 | import parseBindings from '../parsebindings'; 12 | import select from '../select'; 13 | import selectAll from '../selectall'; 14 | import set from '../set'; 15 | import remove from '../remove'; 16 | import instantiate from '../instantiate'; 17 | import mediate from '../mediate'; 18 | 19 | // the following methods can be used as static methods and as instance methods 20 | export { 21 | on, 22 | once, 23 | onDebounce, 24 | off, 25 | trigger, 26 | calc, 27 | bindNode, 28 | unbindNode, 29 | bindOptionalNode, 30 | bindSandbox, 31 | parseBindings, 32 | select, 33 | selectAll, 34 | set, 35 | remove, 36 | instantiate, 37 | mediate 38 | }; 39 | -------------------------------------------------------------------------------- /packages/seemple/src/seemple/index.js: -------------------------------------------------------------------------------- 1 | import Class from '../class'; 2 | import staticMembers from './_staticmembers'; 3 | import instanceMembers from './_prototype'; 4 | import initSeemple from '../_core/init'; 5 | import seempleError from '../_helpers/seempleerror'; 6 | 7 | instanceMembers.constructor = function Seemple() { 8 | if (!(this instanceof Seemple)) { 9 | throw seempleError('common:call_class'); 10 | } 11 | 12 | initSeemple(this); 13 | }; 14 | 15 | const Seemple = Class(instanceMembers, staticMembers); 16 | 17 | export default Seemple; 18 | -------------------------------------------------------------------------------- /packages/seemple/src/select.js: -------------------------------------------------------------------------------- 1 | import defs from './_core/defs'; 2 | import selectNodes from './bindnode/_selectnodes'; 3 | import checkObjectType from './_helpers/checkobjecttype'; 4 | 5 | const customSelectorTestReg = /:sandbox|:bound\(([^(]*)\)/; 6 | 7 | // selects one node based on given selector 8 | export default function select(object, selector) { 9 | if (typeof this === 'object' && this.isSeemple) { 10 | // when context is Seemple instance, use this as an object and shift other args 11 | /* eslint-disable no-param-reassign */ 12 | selector = object; 13 | object = this; 14 | /* eslint-enable no-param-reassign */ 15 | } else { 16 | // throw error when object type is wrong 17 | checkObjectType(object, 'select'); 18 | } 19 | 20 | // the selector includes "custom" things like :sandbox or :bound(KEY) 21 | if (customSelectorTestReg.test(selector)) { 22 | return selectNodes(object, selector)[0] || null; 23 | } 24 | const def = defs.get(object); 25 | 26 | if (!def || typeof selector !== 'string') { 27 | return null; 28 | } 29 | 30 | const propDef = def.props.sandbox; 31 | 32 | if (!propDef) { 33 | return null; 34 | } 35 | 36 | const { bindings } = propDef; 37 | 38 | if (bindings) { 39 | // iterate over all bound nodes trying to find a descendant matched given selector 40 | for (let i = 0; i < bindings.length; i++) { 41 | const node = bindings[i].node; 42 | const selected = node.querySelector(selector); 43 | 44 | if (selected) { 45 | return selected; 46 | } 47 | } 48 | } 49 | 50 | return null; 51 | } 52 | -------------------------------------------------------------------------------- /packages/seemple/src/selectall.js: -------------------------------------------------------------------------------- 1 | import defs from './_core/defs'; 2 | import dom from './_dom'; 3 | import selectNodes from './bindnode/_selectnodes'; 4 | import toArray from './_helpers/toarray'; 5 | import checkObjectType from './_helpers/checkobjecttype'; 6 | import forEach from './_helpers/foreach'; 7 | 8 | const customSelectorTestReg = /:sandbox|:bound\(([^(]*)\)/; 9 | 10 | // selects nodes based on given selector 11 | export default function selectAll(object, selector) { 12 | if (typeof this === 'object' && this.isSeemple) { 13 | // when context is Seemple instance, use this as an object and shift other args 14 | /* eslint-disable no-param-reassign */ 15 | selector = object; 16 | object = this; 17 | /* eslint-enable no-param-reassign */ 18 | } else { 19 | // throw error when object type is wrong 20 | checkObjectType(object, 'selectAll or $'); 21 | } 22 | 23 | // the selector includes "custom" things like :sandbox or :bound(KEY) 24 | if (customSelectorTestReg.test(selector)) { 25 | return selectNodes(object, selector); 26 | } 27 | 28 | const def = defs.get(object); 29 | let result = dom.$(); 30 | 31 | if (!def || typeof selector !== 'string') { 32 | return result; 33 | } 34 | 35 | const propDef = def.props.sandbox; 36 | 37 | if (!propDef) { 38 | return result; 39 | } 40 | 41 | const { bindings } = propDef; 42 | 43 | if (bindings) { 44 | // iterate over all bindings and add found nodes 45 | forEach(bindings, ({ node }) => { 46 | const selected = node.querySelectorAll(selector); 47 | result = result.add(toArray(selected)); 48 | }); 49 | } 50 | 51 | return result; 52 | } 53 | -------------------------------------------------------------------------------- /packages/seemple/src/toseemple.js: -------------------------------------------------------------------------------- 1 | import forEach from './_helpers/foreach'; 2 | import forOwn from './_helpers/forown'; 3 | 4 | // recursively converts objects and arrays to Seemple.Object and Seemple.Array instances 5 | export default function toSeemple(data) { 6 | // fix circular ref issue 7 | const SeempleObject = require('./object').default; 8 | const SeempleArray = require('./array').default; 9 | 10 | // convert only objects 11 | if (data && typeof data === 'object') { 12 | if ('length' in data) { 13 | // if length is given convert it to Seemple.Array instance 14 | const arrayItems = Array(data.length); 15 | 16 | forEach(data, (item, index) => { 17 | arrayItems[index] = toSeemple(item); 18 | }); 19 | 20 | return new SeempleArray().recreate(arrayItems); 21 | } 22 | 23 | // if length is not given convert it to Seemple.Object instance 24 | const object = {}; 25 | 26 | forOwn(data, (value, key) => { 27 | object[key] = toSeemple(value); 28 | }); 29 | 30 | return new SeempleObject(object); 31 | } 32 | 33 | // for all non-objects just return passed data 34 | return data; 35 | } 36 | -------------------------------------------------------------------------------- /packages/seemple/src/trigger/_triggerdomevent.js: -------------------------------------------------------------------------------- 1 | import triggerOneDOMEvent from './_triggeronedomevent'; 2 | import defs from '../_core/defs'; 3 | import forEach from '../_helpers/foreach'; 4 | 5 | // triggers DOM event on bound nodes 6 | export default function triggerDOMEvent(object, key, eventName, selector, triggerArgs) { 7 | const def = defs.get(object); 8 | 9 | if (!def) { 10 | return; 11 | } 12 | 13 | const { props } = def; 14 | const propDef = props[key]; 15 | 16 | if (!propDef) { 17 | return; 18 | } 19 | 20 | const { bindings } = propDef; 21 | 22 | if (!bindings) { 23 | return; 24 | } 25 | 26 | forEach(bindings, ({ node }) => { 27 | if (selector) { 28 | // if selector is given trigger an event on all node descendants 29 | const descendants = node.querySelectorAll(selector); 30 | forEach(descendants, (descendant) => { 31 | triggerOneDOMEvent({ 32 | node: descendant, 33 | eventName, 34 | triggerArgs 35 | }); 36 | }); 37 | } else { 38 | // trigger an event for single node 39 | triggerOneDOMEvent({ 40 | node, 41 | eventName, 42 | triggerArgs 43 | }); 44 | } 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /packages/seemple/src/trigger/_triggerone.js: -------------------------------------------------------------------------------- 1 | import defs from '../_core/defs'; 2 | import apply from '../_helpers/apply'; 3 | 4 | // triggers one event 5 | export default function triggerOne(object, name, triggerArgs) { 6 | const def = defs.get(object); 7 | const events = def && def.events[name]; 8 | 9 | if (events) { 10 | const l = events.length; 11 | let i = 0; 12 | 13 | // allow to pass both array of args and single arg as triggerArgs 14 | if (triggerArgs instanceof Array) { 15 | while (i < l) { 16 | const event = triggerOne.latestEvent = events[i]; 17 | const { callback, ctx } = event; 18 | apply(callback, ctx, triggerArgs); 19 | i += 1; 20 | } 21 | } else { 22 | while (i < l) { 23 | const event = triggerOne.latestEvent = events[i]; 24 | const { callback, ctx } = event; 25 | callback.call(ctx, triggerArgs); 26 | i += 1; 27 | } 28 | } 29 | } 30 | } 31 | 32 | // latestEvent is used as required hack in somemethods 33 | triggerOne.latestEvent = { 34 | info: {}, 35 | name: null 36 | }; 37 | -------------------------------------------------------------------------------- /packages/seemple/src/trigger/_triggeronedomevent.js: -------------------------------------------------------------------------------- 1 | // triggers given DOM event on given node 2 | export default function triggerOneDOMEvent({ 3 | node, 4 | eventName, 5 | triggerArgs 6 | }) { 7 | const { document, Event } = window; 8 | let event; 9 | 10 | // polyfill for older browsers 11 | if (document.createEvent) { 12 | /* istanbul ignore next */ 13 | event = document.createEvent('Event'); 14 | event.initEvent(eventName, true, true); 15 | } else if (typeof Event !== 'undefined') { 16 | event = new Event(eventName, { 17 | bubbles: true, 18 | cancelable: true 19 | }); 20 | } 21 | 22 | // seempleTriggerArgs will be used in a handler created by addDOMListener 23 | event.seempleTriggerArgs = triggerArgs; 24 | 25 | node.dispatchEvent(event); 26 | } 27 | -------------------------------------------------------------------------------- /packages/seemple/src/trigger/index.js: -------------------------------------------------------------------------------- 1 | import domEventReg from '../on/_domeventregexp'; 2 | import checkObjectType from '../_helpers/checkobjecttype'; 3 | import seempleError from '../_helpers/seempleerror'; 4 | import forEach from '../_helpers/foreach'; 5 | import splitBySpaceReg from '../on/_splitbyspaceregexp'; 6 | import defs from '../_core/defs'; 7 | import triggerOne from './_triggerone'; 8 | import triggerDomEvent from './_triggerdomevent'; 9 | 10 | // triggers an event 11 | export default function trigger(...args) { 12 | let object; 13 | let givenNames; 14 | let triggerArgs; 15 | 16 | if (typeof this === 'object' && this.isSeemple) { 17 | // when context is Seemple instance, use this as an object and shift other args 18 | [givenNames, ...triggerArgs] = args; 19 | object = this; 20 | } else { 21 | [object, givenNames, ...triggerArgs] = args; 22 | // throw error when object type is wrong 23 | checkObjectType(object, 'trigger'); 24 | } 25 | let names; 26 | 27 | // allow to use strings only as event name 28 | if (typeof givenNames === 'string') { 29 | names = givenNames.split(splitBySpaceReg); 30 | } else { 31 | throw seempleError('trigger:names_type', { names: givenNames }); 32 | } 33 | 34 | const def = defs.get(object); 35 | 36 | // if no definition do nothing 37 | if (!def) { 38 | return object; 39 | } 40 | 41 | const { events: allEvents } = def; 42 | 43 | if (!allEvents) { 44 | return object; 45 | } 46 | 47 | forEach(names, (name) => { 48 | const domEvtExecResult = domEventReg.exec(name); 49 | 50 | if (domEvtExecResult) { 51 | // if EVT::KEY(SELECTOR) ia passed as event name then trigger DOM event 52 | const [, eventName, key = 'sandbox', selector] = domEvtExecResult; 53 | triggerDomEvent(object, key, eventName, selector, triggerArgs); 54 | } else { 55 | // trigger ordinary event 56 | triggerOne(object, name, triggerArgs); 57 | } 58 | }); 59 | 60 | return object; 61 | } 62 | -------------------------------------------------------------------------------- /packages/seemple/src/unbindnode/_removebinding.js: -------------------------------------------------------------------------------- 1 | import removeListener from '../off/_removelistener'; 2 | import triggerOne from '../trigger/_triggerone'; 3 | import forEach from '../_helpers/foreach'; 4 | 5 | const spaceReg = /\s+/; 6 | 7 | // the function removes single binding for single object 8 | // called by unbindNode 9 | export default function removeBinding({ 10 | object, 11 | key, 12 | eventOptions, 13 | binding 14 | }) { 15 | const { 16 | bindingOptions, 17 | binder, 18 | node, 19 | nodeHandler, 20 | objectHandler 21 | } = binding; 22 | const { destroy, on } = binder; 23 | const { silent } = eventOptions; 24 | 25 | // if "on" is a function then disable it 26 | // we cannot "turn off" custom listener defined by a programmer 27 | // programmer needs to remove custom listener maually inside binder.destroy 28 | if (typeof on === 'function') { 29 | nodeHandler.disabled = true; 30 | } else if (typeof on === 'string') { 31 | // remove DOM event listener 32 | // removeEventListener is faster than "on" method from any DOM library 33 | forEach( 34 | on.split(spaceReg), 35 | (evtName) => node.removeEventListener(evtName, nodeHandler) 36 | ); 37 | } 38 | 39 | // remove object event listener 40 | removeListener(object, `_change:bindings:${key}`, objectHandler); 41 | 42 | // if binder.destroy is given call it 43 | if (destroy) { 44 | destroy.call(node, bindingOptions); 45 | } 46 | 47 | // fire events 48 | if (!silent) { 49 | const extendedEventOptions = { 50 | key, 51 | node, 52 | ...eventOptions 53 | }; 54 | 55 | triggerOne(object, `unbind:${key}`, extendedEventOptions); 56 | triggerOne(object, 'unbind', extendedEventOptions); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/seemple/src/usedomlibrary.js: -------------------------------------------------------------------------------- 1 | import dom from './_dom'; 2 | import mq from './_dom/mq'; 3 | 4 | // forces Matrsahka to use jQuery-like DOM library for internal stuff 5 | export default function useDOMLibrary(library) { 6 | if (typeof library === 'function') { 7 | dom.$ = library; 8 | } else { 9 | dom.$ = mq; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/seemple/test/browser-test/SpecRunner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Jasmine Spec Runner v2.4.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /packages/seemple/test/browser-test/jasmine-2.4.1/jasmine_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/finom/seemple/1721ba823bca6169e7d87f735423d86d1d43152d/packages/seemple/test/browser-test/jasmine-2.4.1/jasmine_favicon.png -------------------------------------------------------------------------------- /packages/seemple/test/helpers/createspy.js: -------------------------------------------------------------------------------- 1 | export default function createSpy(spy = () => {}) { 2 | const spyName = 'function'; 3 | const spyObj = {}; 4 | spyObj[spyName] = spy; 5 | return spyOn(spyObj, spyName).and.callThrough(); 6 | } 7 | -------------------------------------------------------------------------------- /packages/seemple/test/helpers/deepfind.js: -------------------------------------------------------------------------------- 1 | export default function (obj, path) { 2 | const paths = typeof path === 'string' ? path.split('.') : path; 3 | let current = obj; 4 | 5 | for (let i = 0; i < paths.length; ++i) { 6 | if (typeof current[paths[i]] === 'undefined') { 7 | return undefined; 8 | } 9 | 10 | current = current[paths[i]]; 11 | } 12 | 13 | return current; 14 | } 15 | -------------------------------------------------------------------------------- /packages/seemple/test/helpers/makeobject.js: -------------------------------------------------------------------------------- 1 | // creates nested object based on path and lastValue 2 | // example: makeObject('a.b.c', 42) -> {a: {b: {c; 42}}} 3 | export default function makeObject(givenPath = '', lastValue = {}) { 4 | const path = givenPath ? givenPath.split('.') : []; 5 | const result = {}; 6 | let obj = result; 7 | let key; 8 | 9 | 10 | while (path.length > 1) { 11 | key = path.shift(); 12 | obj = obj[key] = {}; 13 | } 14 | 15 | obj[path.shift()] = lastValue; 16 | 17 | return result; 18 | } 19 | -------------------------------------------------------------------------------- /packages/seemple/test/helpers/simulateclick.js: -------------------------------------------------------------------------------- 1 | // simulates click on a node 2 | export default function simulateClick(node) { 3 | const evt = window.document.createEvent('MouseEvent'); 4 | evt.initMouseEvent( 5 | 'click', true, true, window, 0, 0, 0, 0, 0, 6 | false, false, false, false, 0, null 7 | ); 8 | node.dispatchEvent(evt); 9 | } 10 | -------------------------------------------------------------------------------- /packages/seemple/test/index.js: -------------------------------------------------------------------------------- 1 | 2 | // This gets replaced by karma webpack with the updated files on rebuild 3 | const __karmaWebpackManifest__ = []; 4 | 5 | // require all modules from the 6 | // current directory and all subdirectories 7 | const testsContext = require.context('./spec/', true, /.*\.js$/); 8 | 9 | function inManifest(path) { 10 | return __karmaWebpackManifest__.indexOf(path) >= 0; 11 | } 12 | 13 | let runnable = testsContext.keys().filter(inManifest); 14 | 15 | // Run all tests if we didn't find any changes 16 | if (!runnable.length) { 17 | runnable = testsContext.keys(); 18 | } 19 | 20 | runnable.forEach(testsContext); 21 | 22 | const componentsContext = require.context('../src/', true, /.*\.js$/); 23 | componentsContext.keys().forEach(componentsContext); 24 | 25 | // TO REPLACE FILE NAMES USE find . -exec rename 's|foo|bar|' {} + 26 | -------------------------------------------------------------------------------- /packages/seemple/test/node-test/jasmine.js: -------------------------------------------------------------------------------- 1 | const Jasmine = require('jasmine'); 2 | const path = require('path'); 3 | const { JSDOM } = require('jsdom'); 4 | const appModulePath = require('app-module-path'); 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | const jasmine = new Jasmine(); 8 | 9 | global.window = new JSDOM('', { 10 | url: 'http://localhost' 11 | }).window; 12 | 13 | appModulePath.addPath(path.resolve(__dirname, '../..')); 14 | 15 | jasmine.loadConfig({ 16 | spec_dir: 'test/spec', 17 | spec_files: [ 18 | '**/*_spec.js' 19 | ] 20 | }); 21 | 22 | jasmine.addReporter(new SpecReporter()); 23 | 24 | jasmine.execute(); 25 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/chain_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import chain from 'src/chain'; 3 | 4 | describe('chain', () => { 5 | it('has all needed methods', () => { 6 | const inst = chain({}); 7 | 8 | `on, 9 | once, 10 | onDebounce, 11 | off, 12 | trigger, 13 | calc, 14 | bindNode, 15 | unbindNode, 16 | bindOptionalNode, 17 | bindSandbox, 18 | parseBindings, 19 | select, 20 | selectAll, 21 | set, 22 | remove, 23 | instantiate, 24 | mediate`.split(/\s*,\s*/) 25 | .forEach((name) => { 26 | expect(typeof inst[name]).toEqual('function'); 27 | }); 28 | }); 29 | 30 | it('can call calc and set as proof of chain work', () => { 31 | const obj = { a: 1 }; 32 | chain(obj) 33 | .calc('b', 'a', (a) => a * 2, { debounceCalc: false }) 34 | .set('a', 2); 35 | 36 | expect(obj.b).toEqual(4); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/class_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import Class from 'src/class'; 3 | 4 | describe('Class function', () => { 5 | const symbolIt = typeof Symbol === 'function' ? it : xit; 6 | 7 | it('allows to inherit', () => { 8 | const A = Class({ a: true }); 9 | const B = Class({ b: true, extends: A }); 10 | const C = Class({ c: true, extends: B }); 11 | const inst = new C(); 12 | 13 | expect(inst instanceof A).toBeTruthy(); 14 | expect(inst instanceof B).toBeTruthy(); 15 | expect(inst instanceof C).toBeTruthy(); 16 | 17 | expect(inst.a).toBeTruthy(); 18 | expect(inst.b).toBeTruthy(); 19 | expect(inst.c).toBeTruthy(); 20 | }); 21 | 22 | symbolIt('allows to inherit symbols', () => { 23 | const a = Symbol('a'); 24 | const b = Symbol('b'); 25 | const c = Symbol('c'); 26 | 27 | const A = Class({ [a]: true }); 28 | const B = Class({ [b]: true, extends: A }); 29 | const C = Class({ [c]: true, extends: B }); 30 | const inst = new C(); 31 | 32 | expect(inst[a]).toBeTruthy(); 33 | expect(inst[a]).toBeTruthy(); 34 | expect(inst[c]).toBeTruthy(); 35 | }); 36 | 37 | it('allows to pass static props', () => { 38 | const A = Class({}, { staticProp: true }); 39 | expect(A.staticProp).toBeTruthy(); 40 | }); 41 | 42 | it('allows to inherit static props', () => { 43 | const A = Class({}, { staticProp: true }); 44 | const B = Class({ extends: A }); 45 | expect(B.staticProp).toBeTruthy(); 46 | }); 47 | 48 | symbolIt('allows to pass symbols as static props', () => { 49 | const staticProp = Symbol('staticProp'); 50 | const A = Class({}, { [staticProp]: true }); 51 | expect(A[staticProp]).toBeTruthy(); 52 | }); 53 | 54 | symbolIt('allows to inherit symbols as static props', () => { 55 | const staticProp = Symbol('staticProp'); 56 | const A = Class({}, { [staticProp]: true }); 57 | const B = Class({ extends: A }); 58 | expect(B[staticProp]).toBeTruthy(); 59 | }); 60 | 61 | it('if new Class({}) is called return its instance', () => { 62 | const inst = new Class({ a: true }); 63 | expect(inst.a).toEqual(true); 64 | expect(inst instanceof Class).toBeFalsy(); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/events/events_change_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import addListener from 'src/on/_addlistener'; 3 | import delegateListener from 'src/on/_delegatelistener'; 4 | import undelegateListener from 'src/off/_undelegatelistener'; 5 | import removeListener from 'src/off/_removelistener'; 6 | import makeObject from '../../helpers/makeobject'; 7 | import createSpy from '../../helpers/createspy'; 8 | 9 | describe('Change event (simple and delegated)', () => { 10 | let handler; 11 | 12 | beforeEach(() => { 13 | handler = createSpy(); 14 | }); 15 | 16 | it('fires simple', () => { 17 | const obj = { x: 1 }; 18 | 19 | addListener(obj, 'change:x', handler); 20 | obj.x = 2; 21 | expect(handler).toHaveBeenCalled(); 22 | }); 23 | 24 | it('fires delegated (a.x)', () => { 25 | const obj = makeObject('a.x', 1); 26 | 27 | delegateListener(obj, 'a', 'change:x', handler); 28 | obj.a.x = 2; 29 | expect(handler).toHaveBeenCalled(); 30 | }); 31 | 32 | it('fires delegated (a.b.x)', () => { 33 | const obj = makeObject('a.b.x', 1); 34 | 35 | delegateListener(obj, 'a.b', 'change:x', handler); 36 | obj.a.b.x = 2; 37 | expect(handler).toHaveBeenCalled(); 38 | }); 39 | 40 | it('removes simple', () => { 41 | const obj = { x: 1 }; 42 | 43 | addListener(obj, 'change:x', handler); 44 | removeListener(obj, 'change:x', handler); 45 | obj.x = 2; 46 | expect(handler).not.toHaveBeenCalled(); 47 | }); 48 | 49 | it('removes delegated (a.x)', () => { 50 | const obj = makeObject('a.x', 1); 51 | 52 | delegateListener(obj, 'a', 'change:x', handler); 53 | undelegateListener(obj, 'a', 'change:x', handler); 54 | obj.a.x = 2; 55 | expect(handler).not.toHaveBeenCalled(); 56 | }); 57 | 58 | it('removes delegated (a.b.x)', () => { 59 | const obj = makeObject('a.b.x', 1); 60 | 61 | delegateListener(obj, 'a.b', 'change:x', handler); 62 | undelegateListener(obj, 'a.b', 'change:x', handler); 63 | obj.a.b.x = 2; 64 | expect(handler).not.toHaveBeenCalled(); 65 | }); 66 | 67 | it('fires delegated (a.b.x)', () => { 68 | const obj = makeObject('a.b.x', 1); 69 | 70 | delegateListener(obj, 'a.b', 'change:x', handler); 71 | obj.a.b.x = 2; 72 | expect(handler).toHaveBeenCalled(); 73 | }); 74 | 75 | it('accepts null target (a.b.c, reassign b)', () => { 76 | const obj = makeObject('a.b.c.x', 1); 77 | delegateListener(obj, 'a.b.c', 'someevent', handler); 78 | 79 | expect(() => { 80 | obj.a.b = null; 81 | }).not.toThrow(); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/mediate_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import mediate from 'src/mediate'; 3 | 4 | describe('mediate', () => { 5 | it('mediates', () => { 6 | const obj = {}; 7 | 8 | mediate(obj, 'a', (v) => Number(v)); 9 | mediate(obj, ['b', 'c'], (v) => Number(v)); 10 | 11 | obj.a = obj.b = obj.c = '123'; 12 | 13 | expect(typeof obj.a).toEqual('number'); 14 | expect(typeof obj.b).toEqual('number'); 15 | expect(typeof obj.c).toEqual('number'); 16 | }); 17 | 18 | it('mediates in context of an object which has isSeemple=true property', () => { 19 | const obj = { isSeemple: true }; 20 | 21 | mediate.call(obj, 'a', (v) => Number(v)); 22 | mediate.call(obj, ['b', 'c'], (v) => Number(v)); 23 | 24 | obj.a = obj.b = obj.c = '123'; 25 | 26 | expect(typeof obj.a).toEqual('number'); 27 | expect(typeof obj.b).toEqual('number'); 28 | expect(typeof obj.c).toEqual('number'); 29 | }); 30 | 31 | it('mediates using key-mediator object', () => { 32 | const obj = {}; 33 | 34 | mediate(obj, { 35 | a: (v) => Number(v), 36 | b: (v) => Number(v) 37 | }); 38 | 39 | obj.a = obj.b = '123'; 40 | 41 | expect(typeof obj.a).toEqual('number'); 42 | expect(typeof obj.b).toEqual('number'); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/mq/add_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import $ from 'src/_dom/mq'; 3 | 4 | describe('mq.fn.add', () => { 5 | it('adds once', () => { 6 | const el1 = window.document.createElement('div'); 7 | const el2 = window.document.createElement('div'); 8 | const el3 = window.document.createElement('div'); 9 | const el4 = window.document.createElement('div'); 10 | const el5 = window.document.createElement('div'); 11 | const result = Array.from($([el1, el2, el3]).add([el2, el3, el4, el5])); 12 | 13 | expect(result).toEqual([el1, el2, el3, el4, el5]); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/mq/init_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import $ from 'src/_dom/mq'; 3 | 4 | describe('mq initialization', () => { 5 | let testSandbox; 6 | 7 | beforeEach(() => { 8 | testSandbox = window.document.createElement('div'); 9 | 10 | testSandbox.innerHTML = ` 11 |
12 |
13 |
14 |
15 |
16 | `; 17 | }); 18 | 19 | it('accepts window', () => { 20 | const result = $(window); 21 | expect(result.length).toEqual(1); 22 | expect(result[0]).toEqual(window); 23 | }); 24 | 25 | it('accepts document', () => { 26 | const result = $(window.document); 27 | expect(result.length).toEqual(1); 28 | expect(result[0]).toEqual(window.document); 29 | }); 30 | 31 | it('parses HTML', () => { 32 | const result = $('
'); 33 | 34 | expect(result.length).toEqual(2); 35 | expect(result[0].tagName).toEqual('DIV'); 36 | expect(result[1].tagName).toEqual('SPAN'); 37 | }); 38 | 39 | it('converts array-like', () => { 40 | const children = testSandbox.querySelectorAll('*'); 41 | const result = $(children); 42 | 43 | expect(children.length).toEqual(result.length); 44 | 45 | for (let i = 0; i < children.length; i++) { 46 | expect(children[i]).toEqual(result[i]); 47 | } 48 | }); 49 | 50 | it('converts one element', () => { 51 | const element = window.document.querySelector('*'); 52 | const result = $(element); 53 | 54 | expect(result.length).toEqual(1); 55 | expect(element).toEqual(result[0]); 56 | }); 57 | 58 | it('uses context', () => { 59 | expect($('.test-1', testSandbox).length).toEqual(1); 60 | }); 61 | 62 | it('does not use wrong context', () => { 63 | expect($('.test-1', '.wrong-context').length).toEqual(0); 64 | }); 65 | 66 | it('allows to pass null', () => { 67 | expect($(null).length).toEqual(0); 68 | }); 69 | 70 | it('allows to pass nothing', () => { 71 | expect($().length).toEqual(0); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/mq/parsehtml_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import $ from 'src/_dom/mq'; 3 | 4 | describe('mq.parseHTML', () => { 5 | it('parses HTML', () => { 6 | const result = $.parseHTML('
'); 7 | 8 | expect(result.length).toEqual(2); 9 | expect(result[0].tagName).toEqual('DIV'); 10 | expect(result[1].tagName).toEqual('SPAN'); 11 | }); 12 | 13 | it('parses contextual elements', () => { 14 | const result = $.parseHTML(''); 15 | 16 | expect(result.length).toEqual(2); 17 | expect(result[0].tagName).toEqual('TD'); 18 | expect(result[1].tagName).toEqual('TD'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/remove_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import remove from 'src/remove'; 3 | import on from 'src/on'; 4 | import bindNode from 'src/bindnode'; 5 | import trigger from 'src/trigger'; 6 | import select from 'src/select'; 7 | import createSpy from '../helpers/createspy'; 8 | 9 | describe('remove', () => { 10 | it('removes a property', () => { 11 | const obj = { 12 | a: 1 13 | }; 14 | 15 | remove(obj, 'a'); 16 | expect('a' in obj).toBe(false); 17 | }); 18 | 19 | it('removes a property in context of an object which has isSeemple=true property', () => { 20 | const obj = { 21 | a: 1, 22 | isSeemple: true 23 | }; 24 | 25 | remove.call(obj, 'a'); 26 | expect('a' in obj).toBe(false); 27 | }); 28 | 29 | it('removes a property and its events', () => { 30 | const obj = { 31 | a: 1 32 | }; 33 | const handler = createSpy(); 34 | 35 | on(obj, 'change:a', handler); 36 | trigger(obj, 'change:a'); 37 | expect(handler).toHaveBeenCalledTimes(1); 38 | remove(obj, 'a'); 39 | trigger(obj, 'change:a'); 40 | expect(handler).toHaveBeenCalledTimes(1); 41 | expect('a' in obj).toBe(false); 42 | }); 43 | 44 | it('removes a property and its bindings', () => { 45 | const obj = { 46 | a: 1 47 | }; 48 | const node = window.document.createElement('div'); 49 | 50 | bindNode(obj, 'a', node); 51 | expect(select(obj, ':bound(a)')).toEqual(node); 52 | remove(obj, 'a'); 53 | expect(select(obj, ':bound(a)')).toEqual(null); 54 | expect('a' in obj).toBe(false); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/seemple_array/common_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import Seemple from 'src'; 3 | import SeempleArray from 'src/array'; 4 | import createSpy from '../../helpers/createspy'; 5 | 6 | describe('Seemple.Array class', () => { 7 | const methodNames = `_afterInit, 8 | mediateItem, 9 | orderBy, 10 | pull, 11 | recreate, 12 | rerender, 13 | restore, 14 | toJSON, 15 | concat, 16 | join, 17 | pop, 18 | push, 19 | reverse, 20 | shift, 21 | slice, 22 | sort, 23 | splice, 24 | toString, 25 | unshift, 26 | every, 27 | filter, 28 | forEach, 29 | indexOf, 30 | lastIndexOf, 31 | map, 32 | some, 33 | entries, 34 | keys, 35 | values, 36 | copyWithin, 37 | fill, 38 | includes, 39 | find, 40 | findIndex, 41 | push_, 42 | pop_, 43 | unshift_, 44 | shift_, 45 | sort_, 46 | reverse_, 47 | splice_`.split(/\s*,\s*/); 48 | 49 | it('an instance should have isSeemple=true and isSeempleArray=true properties', () => { 50 | const obj = new SeempleArray(); 51 | expect(obj.isSeemple).toEqual(true); 52 | expect(obj.isSeempleArray).toEqual(true); 53 | }); 54 | 55 | it('includes all instance methods', () => { 56 | const obj = new SeempleArray(); 57 | for (let i = 0; i < methodNames.length; i++) { 58 | const name = methodNames[i]; 59 | expect(typeof obj[name]).toEqual('function', `${name} method is missing`); 60 | } 61 | }); 62 | 63 | it('includes all static methods', () => { 64 | expect(typeof SeempleArray.of).toEqual('function', 'of method is missing'); 65 | expect(typeof SeempleArray.from).toEqual('function', 'from method is missing'); 66 | }); 67 | 68 | it('is a property of Seemple', () => { 69 | expect(Seemple.Array).toEqual(SeempleArray); 70 | }); 71 | 72 | it('triggers addone and removeone', () => { 73 | const arr = SeempleArray.of(1, 2, 3, 4, 5); 74 | const addOneHandler = createSpy(({ addedItem }) => { 75 | expect(addedItem).toEqual('foo'); 76 | }); 77 | const removeOneHandler = createSpy(({ removedItem }) => { 78 | expect(removedItem).toEqual(2); 79 | }); 80 | 81 | arr.on('addone', addOneHandler); 82 | arr.on('removeone', removeOneHandler); 83 | 84 | arr.push('foo'); 85 | arr.pull(1); 86 | 87 | expect(addOneHandler).toHaveBeenCalledTimes(1); 88 | expect(removeOneHandler).toHaveBeenCalledTimes(1); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/seemple_array/iterator_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import SeempleArray from 'src/array'; 3 | 4 | describe('Seemple.Array iterator', () => { 5 | const symbolIt = typeof Symbol === 'function' ? it : xit; 6 | 7 | symbolIt('iterates via for..of', () => { 8 | const arr = new SeempleArray(1, 2, 3); 9 | let i = 1; 10 | 11 | for (const item of arr) { 12 | expect(item).toEqual(i); 13 | i += 1; 14 | } 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/seemple_array/mediate_item_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import SeempleArray from 'src/array'; 3 | 4 | describe('Seemple.Array mediate item', () => { 5 | it('allows to set item mediator via mediateItem', () => { 6 | const arr = new SeempleArray('foo', 'bar'); 7 | arr.mediateItem((value) => `x${value}`); 8 | 9 | expect(arr.toJSON(false)).toEqual(['xfoo', 'xbar']); 10 | 11 | arr.push('baz'); 12 | 13 | expect(arr.toJSON(false)).toEqual(['xfoo', 'xbar', 'xbaz']); 14 | 15 | arr.splice(0, 0, 'qux'); 16 | 17 | expect(arr.toJSON(false)).toEqual(['xqux', 'xfoo', 'xbar', 'xbaz']); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/seemple_array/model_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import Class from 'src/class'; 3 | import SeempleArray from 'src/array'; 4 | import SeempleObject from 'src/object'; 5 | 6 | describe('Seemple.Array Model', () => { 7 | it('can use Model and Model gets correct arguments', (done) => { 8 | const item = {}; 9 | 10 | const Model = Class({ 11 | constructor(data, parent, index) { 12 | expect(data === item).toBeTruthy(); 13 | expect(index).toEqual(0); 14 | setTimeout(() => { 15 | expect(parent === arr).toBeTruthy(); // eslint-disable-line no-use-before-define 16 | done(); 17 | }); 18 | } 19 | }); 20 | 21 | const SeempleArrayChild = Class({ 22 | extends: SeempleArray, 23 | get Model() { 24 | return Model; 25 | }, 26 | constructor() { 27 | this.push(item); 28 | } 29 | }); 30 | 31 | const arr = new SeempleArrayChild(); 32 | 33 | expect(arr[0] instanceof Model).toBeTruthy(); 34 | }); 35 | 36 | it('allows to change Model dynamically', () => { 37 | const item = {}; 38 | const arr = new Class({ 39 | extends: SeempleArray, 40 | 41 | constructor() { 42 | this.push({}); 43 | } 44 | }); 45 | 46 | expect(arr[0]).toEqual(item); 47 | 48 | arr.Model = SeempleObject; 49 | 50 | expect(arr[0] instanceof SeempleObject).toBeTruthy(); 51 | }); 52 | 53 | it('throws error if Model has wront type', () => { 54 | expect(() => new Class({ 55 | extends: SeempleArray, 56 | Model: undefined, 57 | constructor() { 58 | this.push({}); 59 | } 60 | })).toThrow(); 61 | 62 | expect(() => new Class({ 63 | extends: SeempleArray, 64 | Model: {}, 65 | constructor() { 66 | this.push({}); 67 | } 68 | })).toThrow(); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/seemple_array/orderby_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import SeempleArray from 'src/array'; 3 | 4 | describe('Seemple.Array orderBy method', () => { 5 | // tests partially taken from lodash 6 | const objects = [ 7 | { a: 'x', b: 3 }, 8 | { a: 'y', b: 4 }, 9 | { a: 'x', b: 1 }, 10 | { a: 'y', b: 2 } 11 | ]; 12 | 13 | it('should sort by a single property by a specified order', () => { 14 | const arr = new SeempleArray(...objects); 15 | 16 | expect(arr.orderBy('a', 'desc').toJSON(false)).toEqual([ 17 | objects[1], 18 | objects[3], 19 | objects[0], 20 | objects[2] 21 | ]); 22 | }); 23 | 24 | it('should sort by multiple properties by specified orders', () => { 25 | const arr = new SeempleArray(...objects); 26 | 27 | expect(arr.orderBy(['a', 'b'], ['desc', 'asc']).toJSON(false)).toEqual([ 28 | objects[3], 29 | objects[1], 30 | objects[2], 31 | objects[0] 32 | ]); 33 | }); 34 | 35 | it('should sort by a property in ascending order when its order is not specified', () => { 36 | const arr = new SeempleArray(...objects); 37 | const falsey = ['', 0, false, NaN, null, undefined]; 38 | 39 | expect(arr.orderBy(['a', 'b']).toJSON(false)).toEqual([ 40 | objects[2], 41 | objects[0], 42 | objects[3], 43 | objects[1] 44 | ]); 45 | 46 | falsey.forEach((order, index) => { 47 | const arr = new SeempleArray(...objects); // eslint-disable-line no-shadow 48 | 49 | expect(arr.orderBy(['a', 'b'], index ? ['desc', order] : ['desc']).toJSON(false)).toEqual([ 50 | objects[3], 51 | objects[1], 52 | objects[2], 53 | objects[0] 54 | ]); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/seemple_array/pull_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import SeempleArray from 'src/array'; 3 | 4 | describe('Seemple.Array pull method', () => { 5 | it('pulls', () => { 6 | const arr = new SeempleArray(); 7 | arr.push('a', 'b', 'c'); 8 | const removed = arr.pull(1); 9 | 10 | expect(removed).toEqual('b'); 11 | 12 | expect(arr.toJSON(false)).toEqual(['a', 'c']); 13 | }); 14 | 15 | it('pulls by given value', () => { 16 | const arr = new SeempleArray(); 17 | const object1 = {}; 18 | const object2 = {}; 19 | const object3 = {}; 20 | 21 | arr.push(object1, object2, object3); 22 | 23 | const removed = arr.pull(object2); 24 | 25 | expect(removed === object2).toBe(true); 26 | 27 | expect(arr.toJSON(false)).toEqual([object1, object3]); 28 | }); 29 | 30 | it('throws an error if wrong type is passed to pull method', () => { 31 | const arr = new SeempleArray(); 32 | 33 | arr.push('a', 'b', 'c'); 34 | 35 | expect(() => arr.pull('foo')).toThrow(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/seemple_array/static_methods_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import SeempleArray from 'src/array'; 3 | import Class from 'src/class'; 4 | 5 | describe('Seemple.Array static methods (of and from)', () => { 6 | it('converts an array to Seemple.Array instance via Seemple.Array.from', () => { 7 | const items = [1, 2, 3]; 8 | const arr = SeempleArray.from(items); 9 | 10 | expect(arr instanceof SeempleArray).toBe(true); 11 | expect(arr.toJSON(false)).toEqual(items); 12 | }); 13 | 14 | it('allows to inherit Seemple.Array.from', () => { 15 | const items = [1, 2, 3]; 16 | const OwnerClass = Class({ extends: SeempleArray }); 17 | const arr = OwnerClass.from(items); 18 | 19 | expect(arr instanceof OwnerClass).toBe(true); 20 | expect(arr.toJSON(false)).toEqual(items); 21 | }); 22 | 23 | it('allows to assign Seemple.Array.from to a variable', () => { 24 | const items = [1, 2, 3]; 25 | const from = SeempleArray.from; 26 | const arr = from(items); 27 | 28 | expect(arr instanceof SeempleArray).toBe(true); 29 | expect(arr.toJSON(false)).toEqual(items); 30 | }); 31 | 32 | it('converts arguments to Seemple.Array instance via Seemple.Array.of', () => { 33 | const items = [1, 2, 3]; 34 | const arr = SeempleArray.of(...items); 35 | 36 | expect(arr instanceof SeempleArray).toBe(true); 37 | expect(arr.toJSON(false)).toEqual(items); 38 | }); 39 | 40 | it('allows to inherit Seemple.Array.of', () => { 41 | const items = [1, 2, 3]; 42 | const OwnerClass = Class({ extends: SeempleArray }); 43 | const arr = OwnerClass.of(...items); 44 | 45 | expect(arr instanceof OwnerClass).toBe(true); 46 | expect(arr.toJSON(false)).toEqual(items); 47 | }); 48 | 49 | it('allows to assign Seemple.Array.of to a variable', () => { 50 | const items = [1, 2, 3]; 51 | const of = SeempleArray.of; 52 | const arr = of(...items); 53 | 54 | expect(arr instanceof SeempleArray).toBe(true); 55 | expect(arr.toJSON(false)).toEqual(items); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/seemple_array/tojson_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import SeempleArray from 'src/array'; 3 | 4 | describe('Seemple.Array toJSON method', () => { 5 | it('is converted to JSON', () => { 6 | const arr = new SeempleArray(1, 2, new SeempleArray(3, 4)); 7 | 8 | expect(arr.toJSON()).toEqual([1, 2, [3, 4]]); 9 | }); 10 | 11 | it('is converted to JSON with recursive=false parameter', () => { 12 | const arr = new SeempleArray(1, 2, new SeempleArray(3, 4)); 13 | 14 | expect(arr.toJSON(false)).toEqual([1, 2, arr[2]]); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/seemple_object/common_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import Seemple from 'src'; 3 | import SeempleObject from 'src/object'; 4 | 5 | describe('Seemple.Object class', () => { 6 | const methodNames = `_afterInit, 7 | setData, 8 | addDataKeys, 9 | removeDataKeys, 10 | isDataKey, 11 | keys, 12 | entries, 13 | values, 14 | keyOf, 15 | toJSON, 16 | each`.split(/\s*,\s*/); 17 | 18 | it('an instance should have isSeemple=true and isSeempleObject=true properties', () => { 19 | const obj = new SeempleObject(); 20 | expect(obj.isSeemple).toEqual(true); 21 | expect(obj.isSeempleObject).toEqual(true); 22 | }); 23 | 24 | it('includes all instance methods', () => { 25 | const obj = new SeempleObject(); 26 | for (let i = 0; i < methodNames.length; i++) { 27 | const name = methodNames[i]; 28 | expect(typeof obj[name]).toEqual('function', `${name} method is missing`); 29 | } 30 | 31 | expect(typeof obj.jset).toEqual('function', 'jset method is missing'); 32 | expect(obj.jset).toEqual(obj.setData); 33 | }); 34 | 35 | it('is a property of Seemple', () => { 36 | expect(Seemple.Object).toEqual(SeempleObject); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/seemple_object/each_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import SeempleObject from 'src/object'; 3 | import createSpy from '../../helpers/createspy'; 4 | 5 | describe('Seemple.Object each', () => { 6 | it('is iterated via each', () => { 7 | const obj = new SeempleObject({ 8 | a: 'foo', 9 | b: 'bar', 10 | c: 'baz' 11 | }); 12 | const keys = ['a', 'b', 'c']; 13 | const values = ['foo', 'bar', 'baz']; 14 | const context = {}; 15 | let i = 0; 16 | const callback = createSpy(function iterate(value, key, itSelf) { 17 | expect(value).toEqual(values[i]); 18 | expect(key).toEqual(keys[i]); 19 | expect(itSelf).toEqual(obj); 20 | expect(this).toEqual(context); 21 | i += 1; 22 | }); 23 | 24 | 25 | obj.each(callback, context); 26 | 27 | expect(callback).toHaveBeenCalledTimes(3); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/seemple_object/iterator_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import Class from 'src/class'; 3 | import SeempleObject from 'src/object'; 4 | 5 | describe('Seemple.Object iterator', () => { 6 | const symbolIt = typeof Symbol === 'function' ? it : xit; 7 | 8 | symbolIt('allows to iterate an instance via for..of', () => { 9 | const obj = new SeempleObject({ 10 | a: 'foo', 11 | b: 'bar', 12 | c: 'baz' 13 | }); 14 | const values = ['foo', 'bar', 'baz']; 15 | let i = 0; 16 | 17 | for (const item of obj) { 18 | expect(item).toEqual(values[i]); 19 | i += 1; 20 | } 21 | }); 22 | 23 | symbolIt('allows to iterate an instance of inherited class via for..of', () => { 24 | const Child = Class({ 25 | extends: SeempleObject, 26 | constructor(data) { 27 | this.setData(data); 28 | } 29 | }); 30 | const obj = new Child({ 31 | a: 'foo', 32 | b: 'bar', 33 | c: 'baz' 34 | }); 35 | const values = ['foo', 'bar', 'baz']; 36 | let i = 0; 37 | 38 | for (const item of obj) { 39 | expect(item).toEqual(values[i]); 40 | i += 1; 41 | } 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/seemple_object/tojson_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import SeempleObject from 'src/object'; 3 | 4 | describe('Seemple.Object toJSON method', () => { 5 | it('is converted to JSON object', () => { 6 | const obj = new SeempleObject({ 7 | a: 42, 8 | b: 'yop', 9 | c: new SeempleObject({ 10 | d: 'ya' 11 | }) 12 | }); 13 | const result = obj.toJSON(); 14 | 15 | expect(Object.keys(result)).toEqual(['a', 'b', 'c']); 16 | expect(result.a).toEqual(42); 17 | expect(result.b).toEqual('yop'); 18 | expect(result.c.d).toEqual('ya'); 19 | expect(result.c).not.toEqual(obj.c); 20 | }); 21 | 22 | it('is converted to JSON with recursive=false parameter', () => { 23 | const obj = new SeempleObject({ 24 | a: 42, 25 | b: 'yop', 26 | c: new SeempleObject({ 27 | d: 'ya' 28 | }) 29 | }); 30 | const result = obj.toJSON(false); 31 | 32 | expect(Object.keys(result)).toEqual(['a', 'b', 'c']); 33 | expect(result.a).toEqual(42); 34 | expect(result.b).toEqual('yop'); 35 | expect(result.c.d).toEqual('ya'); 36 | expect(result.c).toEqual(obj.c); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/set_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import set from 'src/set'; 3 | 4 | describe('set', () => { 5 | it('sets', () => { 6 | const obj = {}; 7 | set(obj, 'x', 42); 8 | expect(obj.x).toEqual(42); 9 | 10 | set(obj, { 11 | y: 1, 12 | z: 2 13 | }); 14 | expect(obj.y).toEqual(1); 15 | expect(obj.z).toEqual(2); 16 | }); 17 | 18 | it('sets a property in context of an object which has isSeemple=true property', () => { 19 | const obj = { isSeemple: true }; 20 | set.call(obj, 'x', 42); 21 | expect(obj.x).toEqual(42); 22 | set.call(obj, { 23 | y: 1, 24 | z: 2 25 | }); 26 | expect(obj.y).toEqual(1); 27 | expect(obj.z).toEqual(2); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/toseemple_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import toSeemple from 'src/toseemple'; 3 | import SeempleObject from 'src/object'; 4 | import SeempleArray from 'src/array'; 5 | 6 | describe('toSeemple function', () => { 7 | it('converts to Seemple via Seemple.toSeemple', () => { 8 | const obj = toSeemple({ 9 | a: 1, 10 | b: [1, 2, 3, { 11 | foo: 'bar' 12 | }] 13 | }); 14 | 15 | expect(obj.constructor).toEqual(SeempleObject); 16 | expect(obj.b.constructor).toEqual(SeempleArray); 17 | expect(obj.b[3].constructor).toEqual(SeempleObject); 18 | 19 | expect(obj.a).toEqual(1); 20 | expect(obj.b[0]).toEqual(1); 21 | expect(obj.b[1]).toEqual(2); 22 | expect(obj.b[2]).toEqual(3); 23 | expect(obj.b[3].foo).toEqual('bar'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/seemple/test/spec/usedomlibrary_spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/extensions */ 2 | import dom from 'src/_dom'; 3 | import mq from 'src/_dom/mq'; 4 | import useDOMLibrary from 'src/usedomlibrary'; 5 | 6 | describe('useDOMLibrary function', () => { 7 | it('allows to change DOM library', () => { 8 | const dummyLibrary = () => {}; 9 | useDOMLibrary(dummyLibrary); 10 | expect(dom.$).toEqual(dummyLibrary); 11 | useDOMLibrary(null); 12 | }); 13 | 14 | it('sets mq as DOM library when falsy is passed', () => { 15 | useDOMLibrary(null); 16 | expect(dom.$).toEqual(mq); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/seemple/test/webpack-test.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 3 | 4 | module.exports = { 5 | devtool: 'source-map', 6 | context: __dirname, 7 | entry: [ 8 | './index' 9 | ], 10 | output: { 11 | path: path.resolve(__dirname, '../../../bundle/test'), 12 | filename: 'bundle.js' 13 | }, 14 | resolve: { 15 | alias: { 16 | src: path.resolve(__dirname, '../src') 17 | } 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.js$/, 23 | exclude: /node_modules/, 24 | use: ['babel-loader'] 25 | } 26 | ] 27 | }, 28 | plugins: [ 29 | new CopyWebpackPlugin([{ 30 | from: path.resolve(__dirname, 'browser-test') 31 | }]) 32 | ] 33 | }; 34 | -------------------------------------------------------------------------------- /packages/seemple/tools/banner-and-footer-webpack-plugin.js: -------------------------------------------------------------------------------- 1 | const ConcatSource = require('webpack-core/lib/ConcatSource'); 2 | 3 | const date = new Date().toUTCString(); 4 | 5 | const banner = `/* 6 | -------------------------------------------------------------- 7 | Seemple.js v${process.env.npm_package_version} (${date}) 8 | JavaScript Framework by Andrey Gubanov http://github.com/finom 9 | Released under the MIT license 10 | More info: https://seemple.io 11 | -------------------------------------------------------------- 12 | */ 13 | 14 | `; 15 | 16 | // a hack to make 2nd global variable 17 | const footer = 'if(typeof Seemple === "function") this.MK = Seemple;'; 18 | 19 | class BannerAndFooterWebpackPlugin { 20 | apply(compiler) { 21 | compiler.plugin('compilation', (compilation) => { 22 | compilation.plugin('optimize-chunk-assets', (chunks, callback) => { 23 | Object.keys(compilation.assets).forEach((file) => { 24 | const newSource = new ConcatSource(banner, compilation.assets[file], footer); 25 | compilation.assets[file] = newSource; 26 | }); 27 | 28 | callback(); 29 | }); 30 | }); 31 | } 32 | } 33 | 34 | module.exports = BannerAndFooterWebpackPlugin; 35 | -------------------------------------------------------------------------------- /packages/seemple/tools/generate-package.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const sourcePackage = require('../package'); 4 | 5 | const npmPackage = { name: 'seemple' }; 6 | const defaultVersion = '0.0.0-auto'; 7 | 8 | if (sourcePackage.version === '0.0.0-auto') { 9 | throw Error(`Package version cannot be "${defaultVersion}"`); 10 | } 11 | 12 | for (const key of [ 13 | 'version', 14 | 'author', 15 | 'repository', 16 | 'license', 17 | 'bugs', 18 | 'homepage', 19 | 'description', 20 | 'dependencies', 21 | 'main' 22 | ]) { 23 | const value = sourcePackage[key]; 24 | if (!value) { 25 | throw Error(`"${key}" is not specified at package.json`); 26 | } 27 | 28 | npmPackage[key] = value; 29 | } 30 | 31 | console.log('generating package.json'); // eslint-disable-line no-console 32 | 33 | const npmPackageString = JSON.stringify(npmPackage, null, '\t'); 34 | 35 | fs.writeFileSync(path.resolve(__dirname, '../npm/package.json'), npmPackageString, { 36 | encoding: 'utf8' 37 | }); 38 | -------------------------------------------------------------------------------- /packages/seemple/webpack.config.js: -------------------------------------------------------------------------------- 1 | const UnminifiedWebpackPlugin = require('unminified-webpack-plugin'); 2 | const path = require('path'); 3 | const BannerAndFooterWebpackPlugin = require('./tools/banner-and-footer-webpack-plugin'); 4 | 5 | 6 | module.exports = { 7 | 8 | entry: './src/index', 9 | output: { 10 | path: path.resolve(__dirname, '../../bundle'), 11 | filename: 'seemple.min.js', 12 | libraryTarget: 'umd', 13 | library: 'Seemple' 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.js$/, 20 | exclude: /node_modules/, 21 | use: ['babel-loader'] 22 | } 23 | ] 24 | }, 25 | plugins: [ 26 | new UnminifiedWebpackPlugin(), 27 | new BannerAndFooterWebpackPlugin() 28 | ] 29 | }; 30 | -------------------------------------------------------------------------------- /test/post-publish/README.md: -------------------------------------------------------------------------------- 1 | The folder contains a test which only checks if all packages correctly deployed to NPM. More specifically, if we didn't mess up with exporting (so you don't need to use `require('foo').default`) and if we've published `/npm` folder instead of package's root. Tests for libraries contain at `/packages/*/test`. 2 | -------------------------------------------------------------------------------- /test/post-publish/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seemple-post-publish", 3 | "dependencies": { 4 | "seemple": "*", 5 | "seemple-parse-form": "*", 6 | "seemple-router": "*" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/post-publish/post-publish.js: -------------------------------------------------------------------------------- 1 | const { execSync } = require('child_process'); 2 | const { JSDOM } = require('jsdom'); 3 | const expect = require('expect.js'); 4 | 5 | 6 | execSync('rm -rf node_modules && npm i --no-package-lock', { cwd: __dirname }); 7 | 8 | global.window = new JSDOM('
', { 9 | url: 'http://localhost' 10 | }).window; 11 | 12 | global.document = global.window.document; 13 | 14 | const Seemple = require('seemple'); 15 | const parseForm = require('seemple-parse-form'); 16 | const Router = require('seemple-router/router'); 17 | const initRouter = require('seemple-router'); 18 | 19 | // check if seemple itself is OK 20 | const seemple = new Seemple(); 21 | seemple.b = 3; 22 | seemple.calc('a', 'b', (b) => b * 2); 23 | expect(seemple.a).to.eql(6); 24 | 25 | // check if seemple-parse-form is OK 26 | parseForm(seemple, global.document.querySelector('form')); 27 | expect(global.document.querySelector('input').value).to.eql('6'); 28 | 29 | // check if seemple-router is OK 30 | const customRouter = new Router('custom'); 31 | customRouter.subscribe(seemple, '/a/'); 32 | expect(customRouter.path).to.eql('/6/'); 33 | expect(typeof initRouter === 'function'); 34 | --------------------------------------------------------------------------------