Browse Source

* init

modules
RubaXa 11 years ago
commit
9d225a0ac8
  1. 6
      .gitignore
  2. 66
      Gruntfile.js
  3. BIN
      favicon.ico
  4. 268
      index.html
  5. 30
      package.json
  6. 138
      ply.css
  7. 1538
      src/Ply.es6
  8. 405
      src/Ply.ui.es6
  9. 149
      st/app.css
  10. 115
      tests/Ply.dom.tests.js
  11. 237
      tests/Ply.effects.tests.js
  12. 250
      tests/Ply.tests.js
  13. 131
      tests/Ply.ui.tests.js
  14. 133
      tests/index.html

6
.gitignore vendored

@ -0,0 +1,6 @@
temp
report
.DS_Store
node_modules
Ply.js
Ply.ui.js

66
Gruntfile.js

@ -0,0 +1,66 @@
'use strict';
module.exports = function (grunt){
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
es6transpiler: {
core: {
src: 'src/Ply.es6',
dest: 'Ply.js'
},
ui: {
src: 'src/Ply.ui.es6',
dest: 'Ply.ui.js'
}
},
watch: {
scripts: {
files: 'src/*.es6',
tasks: ['es6transpiler'],
options: { interrupt: true }
}
},
qunit: {
all: ['tests/*.html'],
options: {
'--web-security': 'no',
coverage: {
src: ['Ply.js', 'Ply.ui.js'],
instrumentedFiles: 'temp/',
htmlReport: 'report/coverage',
coberturaReport: 'report/',
linesThresholdPct: 95,
statementsThresholdPct: 95,
functionsThresholdPct: 95,
branchesThresholdPct: 95
}
}
},
uglify: {
options: {
banner: '/*! <%= pkg.exportName %> <%= pkg.version %> - <%= pkg.license %> | <%= pkg.repository.url %> */\n'
},
dist: {
files: {
'<%= pkg.exportName %>.min.js': ['<%= pkg.exportName %>.js']
}
}
}
});
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-qunit-istanbul');
grunt.loadNpmTasks('grunt-es6-transpiler');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.registerTask('es', ['es6transpiler']);
grunt.registerTask('build', ['es6transpiler', 'qunit']);
grunt.registerTask('min', ['build', 'uglify']);
grunt.registerTask('default', ['build']);
};

BIN
favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

268
index.html

@ -0,0 +1,268 @@
<!DOCTYPE html>
<html>
<head>
<title>Ply — Amazing layer/modal/dialog system. Wow!</title>
<meta name="keywords" content="ply, layer, modal, dialog, javascript, js, rubaxa"/>
<meta name="description" content=""/>
<link rel="icon" href="./favicon.ico" type="image/x-icon"/>
<link rel="shortcut icon" href="./favicon.ico" type="image/x-icon"/>
<link href='http://fonts.googleapis.com/css?family=Lato:300,400' rel='stylesheet' type='text/css'/>
<link href='./st/app.css' rel='stylesheet' type='text/css'/>
<link href='./ply.css' rel='stylesheet' type='text/css'/>
</head>
<body>
<h1 title="Try demo!"><u>Ply</u></h1>
<div class="container">
<div class="row">
<h2>Alert</h2>
<div class="col-left">
<div class="ply-layer alert example">
<div class="ply-content">Hello %username%!</div>
<div class="ply-footer"><button class="ply-ok">OK</button></div>
</div>
</div>
<div class="col-right">
<code>
Ply.dialog("alert", "Hello %username%!");
</code>
</div>
</div>
<div class="row">
<h2>Confirm</h2>
<div class="col-left">
<div class="ply-layer confirm example">
<div class="ply-content">Continue?</div>
<div class="ply-footer"><button class="ply-ok">OK</button><button class="ply-cancel">Cancel</button></div>
</div>
</div>
<div class="col-right">
<code>
Ply.dialog(
"confirm",
{ effect: "3d-sign" },
"Continue?"
);
</code>
</div>
</div>
<div class="row">
<h2>Prompt</h2>
<div class="col-left">
<div class="ply-layer prompt example">
<div class="ply-header">Spam subscribe</div>
<div class="ply-content">
<input class="ply-input" placeholder="E-mail"/>
</div>
<div class="ply-footer"><button class="ply-ok">OK</button><button class="ply-cancel">Cancel</button></div>
</div>
</div>
<div class="col-right">
<code>
Ply.dialog("prompt", {
title: "Spam subscribe",
form: { email: "E-mail" }
});
</code>
</div>
</div>
</div>
<div style="height: 100px"></div>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.0/highlight.min.js"></script>
<script src="./Ply.js"></script>
<script src="./Ply.ui.js"></script>
<script>
jQuery(function ($) {
hljs.configure({ classPrefix: '' });
Ply.dialog({
foo: {
data: 'Foo!',
next: 'bar',
nextEffect: 'inner'
},
bar: {
data: {
text: 'Bar!',
ok: 'Wow!'
}
}
});
$('.example')
.each(function () {
var $el = $(this);
var $code = $el.closest('.row').find('code');
var code = $code.text().split('\n');
var offset = code[1].match(/^\s+/)[0].length;
$.each(code, function (i) {
code[i] = code[i].substr(offset);
});
code = code.slice(1).join('\n');
$code.text(code);
$el.data('code', code);
hljs.highlightBlock($code.wrap('<pre/>').parent().addClass('javascript')[0])
})
.on('click', function () {
Function($(this).data('code'))();
return false;
})
;
$('h1 u').click(function () {
Ply.dialog({
'welcome': {
data: {
children: [{
tag: 'img',
src: 'http://www.saintjn.org/wp-content/uploads/2013/07/welcome.png?1',
width: 400
}],
ok: "Next"
},
next: 'introduce',
nextEffect: '3d-flip[180,-180]'
},
'introduce': {
ui: 'prompt',
data: {
title: 'Your name?',
form: { name: '%username%' }
},
back: 'welcome',
backEffect: '3d-flip[-180,180]',
next: 'hi',
nextEffect: 'scale'
},
'hi': {
data: {
text: 'Hi, {{name}}!',
ok: 'Next'
},
prepare: function (data, dialogs) {
data.text = data.text.replace(/\{\{([^}]+)\}\}/g, function (_, name) {
return dialogs.introduce.val(name);
});
},
back: 'next',
next: 'question',
nextEffect: 'fall'
},
'question': {
ui: 'confirm',
data: {
text: 'You know Jonna Lee?',
ok: 'Yes',
cancel: 'No'
},
back: 'Jonna',
next: 'Jonna-01',
nextEffect: '3d-sign'
},
'Jonna': {
data: 'Jonna Lee (born Jonna Emily Lee Nilsson, October 3, 1981) is a singer-songwriter from Linköping, Sweden, currently residing in Stockholm. Lee is best known for being the creator and artist of iamamiwhoami. Lee started her own label "To whom it may concern" in 2010.',
back: 'next',
next: 'Jonna-01',
nextEffect: 'fade'
},
'Jonna-01': {
data: {
title: 'Jonna Lee',
children: [{
tag: 'img',
src: 'http://www.movieviral.com/wp-content/uploads/2010/03/jonnalee.jpg',
width: 400
}],
ok: 'Next'
},
next: 'Jonna-02',
nextEffect: '3d-flip[180,-180]'
},
'Jonna-02': {
data: {
title: 'Jonna Lee',
children: [{
tag: 'img',
src: 'http://2020k.files.wordpress.com/2012/03/iamamiwhoami-good-worker.png',
width: 400
}],
ok: 'Close',
cancel: 'Rewind'
},
back: 'welcome'
}
});
});
});
</script>
<!-- Background -->
<script>
(function () {
function setNoiseBackground(el, color, width, height, opacity) {
var canvas = document.createElement("canvas");
var context = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
for (var i = 0; i < width; i++) {
for (var j = 0; j < height; j++) {
var val = Math.floor(Math.random() * 255);
context.fillStyle = "rgba(" + val + "," + val + "," + val + "," + opacity + ")";
context.fillRect(i, j, 1, 1);
}
}
el.style.backgroundColor = color;
el.style.backgroundImage = "url(" + canvas.toDataURL("image/png") + ")";
}
// Usage
setNoiseBackground(document.body, "trasparent", 50, 50, 0.02);
})();
</script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-16483888-3', 'rubaxa.github.io');
ga('send', 'pageview');
</script>
</body>
</html>

30
package.json

@ -0,0 +1,30 @@
{
"name": "ply",
"exportName": "Ply",
"version": "0.3.0",
"devDependencies": {
"grunt": "*",
"grunt-contrib-watch": "*",
"grunt-qunit-istanbul": "*",
"grunt-es6-transpiler": "*",
"grunt-contrib-uglify": "*"
},
"description": "Ply — Amazing layer/modal/dialog system. Wow!",
"main": "Ply.js",
"scripts": {
"test": "grunt"
},
"repository": {
"type": "git",
"url": "git://github.com/rubaxa/Ply.git"
},
"keywords": [
"ply",
"layer",
"modal",
"dialog",
"lightbox"
],
"author": "Konstantin Lebedev <ibnRubaXa@gmail.com>",
"license": "MIT"
}

138
ply.css

@ -0,0 +1,138 @@
/* Loading */
.ply-loading {
top: 50%;
left: 50%;
padding: 30px;
width: 60px;
height: 60px;
margin: -100px 0 0 -60px;
z-index: 100000;
position: fixed;
border-radius: 10%;
background-color: rgba(255,255,255,.5);
box-shadow: 0 1px 2px rgba(0,0,0,.2);
}
.ply-loading-spinner {
width: 100%;
height: 100%;
opacity: .9;
background: #fff;
border-radius: 100%;
overflow: hidden;
position: relative;
box-shadow: 0 1px 3px rgba(0,0,0,.6);
}
.ply-loading-spinner::before {
content: "";
display: block;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background: #333;
max-height: 0;
-webkit-animation: loading 3s normal infinite;
animation: loading 3s normal infinite;
}
@keyframes loading {
0% { max-height: 0; }
50% { max-height: 100%; top: 0; }
100% { max-height: 0; top: 120%; }
}
@-webkit-keyframes loading {
0% { max-height: 0; }
50% { max-height: 100%; top: 0; }
100% { max-height: 0; top: 120%; }
}
/* Layer */
.ply-layer {
color: #333;
min-width: 280px;
box-shadow: 0 0 3px rgba(0,0,0,.3);
background-color: #fff;
border-radius: 2px;
font-family: "Arial", Helvetica;
font-size: 16px;
}
.ply-layer.alert .ply-content,
.ply-layer.confirm .ply-content {
padding: 40px 30px;
text-align: center;
}
.ply-layer.alert .ply-footer,
.ply-layer.confirm .ply-footer,
.ply-layer.prompt .ply-footer {
text-align: center;
padding-bottom: 20px;
}
.ply-header {
padding: 10px 20px;
font-size: 18px;
background-color: #f1f1f1;
border-radius: 2px 2px 0 0;
}
.ply-content {
padding: 20px;
}
.ply-footer {
padding: 0 20px 15px;
}
.ply-footer button {
margin-left: 20px;
}
.ply-footer button:first-child {
margin-left: 0;
}
/* Controls */
.ply-ok,
.ply-cancel {
color: #fff;
cursor: pointer;
border: 0;
outline: 0;
padding: 5px 20px;
box-shadow: 0 1px 1px rgba(0,0,0,.2);
background-color: #39C082;
border-radius: 3px;
font-size: 18px;
}
.ply-ok {
width: 100px;
}
.ply-cancel {
background-color: #b2b2b2;
}
.ply-ok:focus,
.ply-cancel:focus {
box-shadow: 0 0 1px 2px rgba(255, 180, 0, .6);
}
/* Forms */
.ply-input {
width: 100%;
border: 2px solid #ccc;
outline: 0;
padding: 5px 10px;
font-size: 16px;
font-family: "Arial", Helvetica;
box-sizing: border-box;
}
.ply-input:focus {
border-color: #39C082;
}

1538
src/Ply.es6

File diff suppressed because it is too large Load Diff

405
src/Ply.ui.es6

@ -0,0 +1,405 @@
/*global define, Ply */
((factory) => {
factory(Ply);
})((Ply) => {
'use strict';
var _plyAttr = Ply.attrName,
noop = Ply.noop,
_each = Ply.each,
_extend = Ply.extend,
_promise = Ply.promise,
_buildDOM = Ply.dom.build,
_appendChild = Ply.dom.appendChild,
_lang = Ply.lang,
_toBlock = (block, name) => {
if (block == null) {
return { skip: true };
}
if (typeof block === 'string') {
block = { text: block };
}
block.name = block.name || name;
return block;
}
;
/**
* Управление рендером UI
* @param {String} name
* @param {Object} [data]
* @param {String} [path]
* @returns {HTMLElement}
*/
function ui(name, data, path) {
var fn = ui[name], el;
if (!fn) {
name = name.split(/\s+/).slice(0, -1).join(' ');
fn = data && (
ui[name + ' [name=' + data.name + ']']
|| ui[name + ' [type=' + data.type + ']']
)
|| ui[name + ' *']
|| ui[':default'];
}
el = _buildDOM(fn(data, path));
if (data && data.name) {
el.setAttribute(_plyAttr + '-name', data.name);
}
el.className += ' ply-ui';
return el;
}
/**
* Назначение визуализатор
* @param {String} name имя фабрики
* @param {Function} renderer
* @param {Boolean} [simpleMode]
*/
ui.factory = function (name, renderer, simpleMode) {
ui[name.trim().replace(/\s+/g, ' ')] = function (data, path) {
var fragment = document.createDocumentFragment();
if ((data != null) || name === ':root') {
data = simpleMode ? data : _toBlock(data);
_each(simpleMode ? data : data.children, function (block, key) {
var abs = ((path || name) + ' ' + key).replace(/^:\w+\s+/, '');
var el = ui(abs, _toBlock(block, key), abs);
_appendChild(fragment, el);
});
if (!simpleMode) {
delete data.children;
}
var result = renderer(data, fragment);
/* istanbul ignore else */
if (!result.appendChild) {
_extend(result, data);
}
return result;
}
return fragment;
};
};
// Элемент по умолчанию
ui.factory(':default', (data, children) => {
data.children = children;
return data;
});
// Ply-слой - корневой элемент
ui.factory(':root', function (data) {
return {
tag: 'form.ply-layer',
className: data.mod,
children: [
ui(':header', data.header),
ui(':content', data.content),
data.ctrls && ui(':default', {
tag: 'div.ply-footer',
children: data.ctrls
})
]
};
});
// «Заголовк» слоя
ui.factory(':header', function (data, children) {
return { tag: '.ply-header', text: data.text, children: children };
});
// «Содержимое» слоя
ui.factory(':content', function (data, children) {
return { tag: '.ply-content', children: children };
}, true);
// Кнопка «ОК»
ui.factory('ok', function (data) {
return {
ply: ':ok',
tag: 'button.ply-ok',
text: data === true ? _lang.ok : data
};
});
// Кнопка «Отмена»
ui.factory('cancel', function (data) {
return {
ply: ':close',
tag: 'button.ply-cancel',
type: 'reset',
text: data === true ? _lang.cancel : data
};
});
/**
* Фабрика слоев
* @param {String} name
* @param {Function} renderer
*/
function factory(name, renderer) {
factory['_' + name] = renderer;
factory[name] = (options, data) => {
return _promise((resolve, reject) => {
renderer(options, data, resolve, reject);
}).then((el) => {
/* istanbul ignore else */
if (!el.appendChild) {
el = ui(':root', el);
}
return el;
});
};
}
/**
* Использовать фабрику
* @param {String} name
* @param {Object} options
* @param {Object} data
* @param {Function} resolve
* @param {Function} [reject]
*/
factory.use = (name, options, data, resolve, reject) => {
factory['_' + name](options, data, resolve, reject);
};
/**
* Абстрактный диалог
* @param {String} mod
* @param {Object} options
* @param {Object} data
* @param {Object} defaults
* @returns {Object}
* @private
*/
function _dialogFactory(mod, options, data, defaults) {
options.effect = options.effect || 'slide';
return {
mod: mod,
header: data.title,
content: data.form
? { 'dialog-form': { children: data.form } }
: { el: data.text || data },
ctrls: {
ok: data.ok || defaults.ok,
cancel: data.cancel || defaults.cancel
}
};
}
// Фабрика по умолчанию
factory('default', (options, data, resolve) => {
resolve(data || /* istanbul ignore next */ {});
});
// Диалог: «Предупреждение»
factory('alert', (options, data, resolve) => {
resolve(_dialogFactory('alert', options, data, { ok: true }));
});
// Диалог: «Подтверждение»
factory('confirm', (options, data, resolve) => {
resolve(_dialogFactory('confirm', options, data, {
ok: true,
cancel: true
}));
});
// Диалог: «Запросить данные»
factory('prompt', (options, data, resolve) => {
resolve(_dialogFactory('prompt', options, data, {
ok: true,
cancel: true
}));
});
// Элемент формы
ui.factory('dialog-form *', (data) => {
return {
tag: 'input.ply-input',
name: data.name,
value: data.value,
required: true,
placeholder: data.hint || data.text
};
});
/**
* Создать Ply-слой на основе фабрики
* @param {String} name название фабрики
* @param {Object} [options] опции
* @param {Object} [data] данные для фабрики
* @returns {Promise}
*/
Ply.create = (name, options, data) => {
if (!data) {
data = options;
options = {};
}
var renderer = (factory[name] || factory['default']);
return renderer(options, data).then((el) => {
return new Ply(_extend(options, { el: el }));
});
};
/**
* Открыть Ply-слой
* @param {String} name
* @param {Object} [options]
* @param {Object} [data]
* @returns {Promise}
*/
Ply.open = (name, options, data) => {
return Ply.create(name, options, data).then((layer) => {
return layer.open();
});
};
/**
* Создать диалог или систему диалогов
* @param {String|Object} name
* @param {Object} [options]
* @param {Object} [data]
* @returns {Promise}
*/
Ply.dialog = (name, options, data) => {
if (name instanceof Object) {
options = options || /* istanbul ignore next */ {};
return _promise((resolve, reject) => {
var first = options.initState,
current,
rootLayer,
stack = name,
dialogs = {},
_progress = (ui, layer) => {
(options.progress || /* istanbul ignore next */ noop)(_extend({
name: current.$name,
index: current.$index,
length: length,
stack: stack,
current: current,
layer: layer
}, ui), dialogs);
},
changeLayer = (spec, effect, callback) => {
// Клонирование данных
var data = JSON.parse(JSON.stringify(spec.data));
current = spec;
(spec.prepare || noop)(data, dialogs);
Ply.create(spec.ui || 'alert', spec.options || {}, data).then((layer) => {
var promise;
if (rootLayer) {
promise = rootLayer.swap(layer, effect);
} else {
promise = layer.open();
rootLayer = layer;
}
promise.then(() => {
dialogs[spec.$name].el = rootLayer.layerEl;
});
callback(layer);
});
}
;
var length = 0;
_each(stack, (spec, key) => {
first = first || key;
spec.effects = spec.effects || {};
spec.$name = key;
spec.$index = length++;
dialogs[key] = new Ply.Context();
});
stack.$length = length;
changeLayer(stack[first], null, (layer) => {
_progress({}, layer);
//noinspection FunctionWithInconsistentReturnsJS
rootLayer.options.callback = (ui) => {
var isNext = ui.state || (current.back === 'next'),
swap = isNext ? stack[current.next] : stack[current.back]
;
if (swap) {
changeLayer(swap, current[isNext ? 'nextEffect' : 'backEffect'], (layer) => {
_progress(ui, layer);
});
return false;
} else {
(ui.state ? resolve : /* istanbul ignore next */ reject)(ui, dialogs);
}
};
});
});
}
else {
if (!data) {
data = options || {};
options = {};
}
return Ply.open(name, options, data).then((layer) => {
return _promise((resolve) => {
layer.options.callback = resolve;
});
});
}
};
// Export
Ply.ui = ui;
Ply.factory = factory;
});

149
st/app.css

@ -0,0 +1,149 @@
html {
background-color: #ac0;
}
html, body {
height: 100%;
}
.container {
margin: 0 auto;
width: 80%;
min-width: 600px;
max-width: 1200px;
}
body, h1, h2 {
margin: 0;
padding: 0;
}
h1, h2 {
color: #fff;
font-family: 'Lato', sans-serif;
font-weight: 300;
text-shadow: 0 1px 1px rgba(0,0,0,.2);
}
h1 {
text-shadow: 0 1px 3px rgba(0,0,0,.2);
font-size: 200px;
text-align: center;
margin-top: 50px;
margin-bottom: 50px;
}
h1 u {
cursor: pointer;
border-bottom: 8px dotted #fff;
text-decoration: none;
}
h2 {
font-size: 30px;
margin-bottom: 10px;
}
.row {
margin-bottom: 30px;
}
.row:after {
clear: both;
content: '';
display: block;
}
.col-left {
float: left;
width: 35%;
padding: 0 20px;
box-sizing: content-box;
}
.col-right {
float: left;
width: 55%;
margin-left: 5%;
}
.example {
cursor: pointer;
opacity: 0.95;
position: relative;
}
.example:hover:after {
top: 50%;
left: 50%;
line-height: 0;
margin: -5px 0 0 -50px;
color: #333;
content: '►';
font-size: 100px;
position: absolute;
display: block;
opacity: .7;
}
.example:hover {
opacity: 1;
}
pre code {
background-color: #fff;
box-shadow: 0 1px 1px rgba(0,0,0,.3);
}
/* Tomorrow Theme */
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
/* Original theme - https://github.com/chriskempson/tomorrow-theme */
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
.tomorrow-comment, pre .comment, pre .title {
color: #8e908c;
}
.tomorrow-red, pre .variable, pre .attribute, pre .tag, pre .regexp, pre .ruby .constant, pre .xml .tag .title, pre .xml .pi, pre .xml .doctype, pre .html .doctype, pre .css .id, pre .css .class, pre .css .pseudo {
color: #c82829;
}
.tomorrow-orange, pre .number, pre .preprocessor, pre .built_in, pre .literal, pre .params, pre .constant {
color: #f5871f;
}
.tomorrow-yellow, pre .class, pre .ruby .class .title, pre .css .rules .attribute {
color: #eab700;
}
.tomorrow-green, pre .string, pre .value, pre .inheritance, pre .header, pre .ruby .symbol, pre .xml .cdata {
color: #718c00;
}
.tomorrow-aqua, pre .css .hexcolor {
color: #3e999f;
}
.tomorrow-blue, pre .function, pre .python .decorator, pre .python .title, pre .ruby .function .title, pre .ruby .title .keyword, pre .perl .sub, pre .javascript .title, pre .coffeescript .title {
color: #4271ae;
}
.tomorrow-purple, pre .keyword, pre .javascript .function {
color: #8959a8;
}
pre {
border: 0;
margin: 0;
padding: 0;
}
pre code {
display: block;
color: #4d4d4c;
font-size: 15px;
font-family: Menlo, Monaco, Consolas, monospace;
line-height: 1.5;
padding: 30px;
}

115
tests/Ply.dom.tests.js

@ -0,0 +1,115 @@
(function (Ply) {
module('Ply.dom');
function elementEqual(actual, expected, msg) {
msg = msg || 'el';
expected.tagName = (expected.tagName || 'div').toUpperCase();
Ply.each(expected, function (value, attr) {
equal(actual[attr] || actual.getAttribute(attr), value, msg + '.' + attr);
});
}
test('build()', function () {
elementEqual(Ply.dom.build(), { });
});
test('build("string")', function () {
Ply.each({
'div': { },
'div#xxx': { id: 'xxx' },
'div.foo': { className: ' foo' },
'div#xxx.foo': { id: 'xxx', className: ' foo' },
'b.foo.bar': { tagName: 'B', className: ' foo bar' },
'span#xxx.foo.bar': { tagName: 'SPAN', id: 'xxx', className: ' foo bar' },
'#xxx': { id: 'xxx' },
'#xxx.foo': { id: 'xxx', className: ' foo' },
'#xxx.foo.bar': { id: 'xxx', className: ' foo bar' }
}, function (data, selector) {
elementEqual(Ply.dom.build(selector), data, selector);
});
});
test('build({ })', function () {
elementEqual(Ply.dom.build({
id: 'baz',
tag: 'input.foo',
className: 'bar',
ply: 'baz',
'data-prop': 'qux'
}), {
id: 'baz',
tagName: 'INPUT',
className: 'bar foo',
'data-ply': 'baz',
'data-prop': 'qux'
}, '{}');
elementEqual(Ply.dom.build({ text: '<b>foo</b>' }), { innerHTML: '&lt;b&gt;foo&lt;/b&gt;' }, 'text');
elementEqual(Ply.dom.build({ html: '<b>bar</b>' }), { innerHTML: '<b>bar</b>' }, 'html');
});
test('build(element)', function () {
var el = Ply.dom.build(Ply.dom.build('b.foo'));
elementEqual(el, { tagName: 'b', className: ' foo' });
});
test('build({ children: {} })', function () {
var el = Ply.dom.build({
tag: 'form',
children: {
'input': {
type: 'password'
},
'hr': true,
'br': false,
'button': 'Enter'
}
});
equal(el.childNodes.length, 3);
elementEqual(el, { tagName: 'form' });
elementEqual(el.childNodes[0], { tagName: 'input', type: 'password' });
elementEqual(el.childNodes[1], { tagName: 'hr' });
elementEqual(el.childNodes[2], { tagName: 'button', innerHTML: 'Enter' });
});
test('build({ children: [] })', function () {
var el = Ply.dom.build({
tag: 'form',
children: [
{
tag: 'input',
type: 'checkbox',
disabled: true
},
{ tag: 'hr', skip: true },
false && { tag: bar },
{ tag: 'button', text: "Enter" }
]
});
equal(el.childNodes.length, 2);
elementEqual(el, { tagName: 'form' });
elementEqual(el.childNodes[0], { tagName: 'input', type: 'checkbox', disabled: true });
elementEqual(el.childNodes[1], { tagName: 'button', innerHTML: 'Enter' });
});
test('build({ children: el })', function () {
var el = Ply.dom.build({
children: Ply.dom.build({ tag: 'b', text: '!' })
});
elementEqual(el, { innerHTML: '<b>!</b>' });
});
})(Ply);

237
tests/Ply.effects.tests.js

@ -0,0 +1,237 @@
(function (Ply) {
module('Ply.effects');
test('core', function () {
Ply.effects.defaults = { duration: 300, open: {}, close: {} };
deepEqual(Ply.effects.get(), {
open: {
layer: { duration: 300 },
overlay: { duration: 300 }
},
close: {
layer: { duration: 300 },
overlay: { duration: 300 }
},
duration: 300
}, 'def');
deepEqual(Ply.effects.get(['fade-in', 'fade-out']), {
open: {
layer: { name: 'fade-in', duration: 300 },
overlay: { name: 'fade-in', duration: 300 }
},
close: {
layer: { name: 'fade-out', duration: 300 },
overlay: { name: 'fade-out', duration: 300 }
},
duration: 300
}, "['fade-in', 'fade-out']");
deepEqual(Ply.effects.get(['fade-in', 'fade-out:100']), {
open: {
layer: { name: 'fade-in', duration: 300 },
overlay: { name: 'fade-in', duration: 300 }
},
close: {
layer: { name: 'fade-out', duration: 100 },
overlay: { name: 'fade-out', duration: 100 }
},
duration: 300
}, "['fade-in', 'fade-out:100']");
deepEqual(Ply.effects.get('fade:100'), {
open: {
layer: { name: 'fade-in', duration: 100 * 0.8 },
overlay: { name: 'fade-in', duration: 100 }
},
close: {
layer: { name: 'fade-out', duration: 100 * 0.6 },
overlay: { name: 'fade-out', duration: 100 * 0.6 }
},
duration: 100
}, 'fade:100');
deepEqual(Ply.effects.get({
open: 'slide-in',
close: 'slide-out'
}), {
open: {
layer: { name: 'slide-in', duration: 300 },
overlay: { duration: 300 }
},
close: {
layer: { name: 'slide-out', duration: 300 },
overlay: { duration: 300 }
},
duration: 300
}, 'slide-in-out');
Ply.effects.setup({ open: 'fade-in' });
deepEqual(Ply.effects.get(), {
open: {
layer: { name: 'fade-in', duration: 300 },
overlay: { duration: 300 }
},
close: {
layer: { duration: 300 },
overlay: { duration: 300 }
},
duration: 300
}, 'open.fade-in');
Ply.effects.setup({ open: { overlay: 'fade-in' } });
deepEqual(Ply.effects.get({
open: 'slide-in',
close: 'slide-out'
}), {
open: {
layer: { name: 'slide-in', duration: 300 },
overlay: { name: 'fade-in', duration: 300 }
},
close: {
layer: { name: 'slide-out', duration: 300 },
overlay: { duration: 300 }
},
duration: 300
});
Ply.effects.setup('fade:400');
deepEqual(Ply.effects.get({
open: 'slide-in',
close: 'slide-out'
}), {
open: {
layer: { name: 'slide-in', duration: 400 },
overlay: { name: 'fade-in', duration: 400 }
},
close: {
layer: { name: 'slide-out', duration: 400 },
overlay: { name: 'fade-out', duration: 400 * 0.6 }
},
duration: 400
}, 'fade:400');
});
test('effects:args', function () {
Ply.effects.defaults = { duration: 300, open: {}, close: {} };
deepEqual(Ply.effects.get('scale[0.5,0.3]'), {
open: {
args: 0.5,
layer: { name: 'scale-in', duration: 300 },
overlay: { name: 'fade-in', duration: 300 }
},
close: {
args: 0.3,
layer: { name: 'scale-out', duration: 300 },
overlay: { name: 'fade-out', duration: 300 }
},
duration: 300
});
deepEqual(Ply.effects.get(['scale["foo"]', 'fall[{"bar":"baz"}]']), {
open: {
args: 'foo',
layer: { name: 'scale-in', duration: 300 },
overlay: { name: 'fade-in', duration: 300 }
},
close: {
args: {bar:'baz'},
layer: { name: 'fall-out', duration: 300 },
overlay: { name: 'fade-out', duration: 300 }
},
duration: 300
});
});
test('stress', function () {
try {
Ply.effects.get(null);
Ply.effects.get(void 0);
Ply.effects.get(Math.random());
Ply.effects.get('---');
Ply.effects.get(123);
Ply.effects.get('\n');
Ply.effects.get([null, null]);
Ply.effects.get([void 0, void 0]);
Ply.effects.get(['\n', '\t']);
ok(true);
} catch (err) {
equal([err, err.stack], null);
}
});
promiseTest('fade', function () {
var log = { open: [], close: [] },
type = 'open',
pid, i,
layer = new Ply({ effect: 'fade' })
;
pid = setInterval(function () {
log[type].push( parseFloat(Ply.css(layer.overlayEl, 'opacity')) );
}, 50);
return layer.open().then(function () {
type = 'close';
return layer.close();
}).then(function () {
ok(log.open.length > 2, 'open');
ok(log.close.length > 2, 'close');
for (i = 1; i < log.open.length; i++) {
if (log.open[i] < log.open[i-1]) {
equal(log.open, null, 'open: ' + i);
break;
}
}
for (i = 1; i < log.close.length; i++) {
if (log.close[i] > log.close[i-1]) {
equal(log.close, null, 'close: ' + i);
break;
}
}
clearInterval(pid);
});
});
promiseTest('other', function () {
expect(0);
var queue = Ply.promise(function (resolve) { resolve() });
Ply.each('scale fall slide 3d-flip 3d-sign'.split(' '), function (name) {
queue = queue.then(function () {
return new Ply({ effect: name + ':50' }).open().then(function (layer) {
return layer.close();
});
});
});
return queue;
});
})(Ply);

250
tests/Ply.tests.js

@ -0,0 +1,250 @@
(function () {
module('Ply');
var layer;
test('core', function () {
equal(typeof Ply, 'function');
ok(new Ply instanceof Ply);
});
function sleep(fn, ms) {
return Ply.promise(function (resolve) {
setTimeout(function () {
fn();
resolve();
}, ms);
});
}
function checkVisiblity(layer, state, msg) {
equal(!!layer.visible, state, '[' + msg + '] visible: ' + state);
equal(!!layer.wrapEl.parentNode, state, msg + ' -> parentNode is ' + (state ? '' : 'not') + 'exists');
}
test('options', function () {
layer = new Ply();
for (var key in Ply.defaults.overlay) {
equal(layer.overlayEl.style[key], Ply.defaults.overlay[key], key);
}
equal(layer.bodyEl, document.body, 'body');
// Body
layer = new Ply({ body: '#playground' });
equal(layer.bodyEl, playground, '#playground');
// Overlay 1
layer = new Ply({ overlay: { opacity: 1 } });
equal(layer.overlayEl.style.opacity, 1, 'opacity: 1');
equal(layer.overlayEl.style.backgroundColor, "", 'backgroundColor: ""');
// Overlay 2
layer = new Ply({ overlay: { opacity: 1, backgroundColor: 'rgb(255, 0, 0)' } });
equal(layer.overlayEl.style.opacity, 1, 'opacity: 1');
equal(layer.overlayEl.style.backgroundColor, 'rgb(255, 0, 0)', 'backgroundColor: red');
// Overlay 3
layer = new Ply({ overlay: null });
notEqual(layer.overlayEl.style.position, 'fixed', 'overlay: null');
// Layer
layer = new Ply({ layer: { textAlign: 'center' } });
equal(layer.contentEl.style.textAlign, 'center', 'textAlign: center');
});
test('flags', function () {
layer = new Ply();
for (var key in Ply.defaults.flags) {
equal(layer.options.flags[key], Ply.defaults.flags[key], key);
}
layer = new Ply({ flags: { bodyScroll: true } });
for (var key in Ply.defaults.flags) {
equal(layer.options.flags[key], key == 'bodyScroll' ? true : Ply.defaults.flags[key], key);
}
});
test('content', function () {
var content = document.createElement('b');
content.innerHTML = '!';
equal(new Ply().contentEl.innerHTML, '');
equal(new Ply({ el: 'Wow!' }).contentEl.innerHTML, 'Wow!');
equal(new Ply({ el: content }).layerEl.innerHTML, '<b>!</b>');
});
promiseTest('open/close', function () {
layer = new Ply({ el: 'Wow!' });
ok(!layer.wrapEl.parentNode, '!parent - open');
ok(!layer.visible, 'visible: false');
return layer.open().then(function () {
var ratio = layer.wrapEl.offsetHeight / (layer.layerEl.offsetTop + layer.layerEl.offsetHeight/2);
ok(layer.visible, 'visible: true');
ok(layer.wrapEl.offsetWidth > 0, 'offsetWidth > 0');
ok(Math.abs(2 - ratio) < 0.1, ratio, 'delat(' + ratio + ') < 0.1');
layer.close().then(function () {
ok(!layer.visible, 'visible: false - close');
ok(!layer.wrapEl.parentNode, '!parent - close');
});
});
});
promiseTest('closeByEsc', function () {
function open(msg, esc) {
var layer = new Ply({ flags: { closeByEsc: esc }, effect: { duration: 1 } });
checkVisiblity(layer, false, msg);
return layer.open().then(function () {
checkVisiblity(layer, true, msg);
return layer;
});
}
return open('1. esc: true', true).then(function (escTrue) {
return open('2. esc: false', false).then(function (escFalse) {
simulateEvent(document, 'keyup', { keyCode: Ply.keys.esc });
checkVisiblity(escTrue, true, '3. esc: true');
checkVisiblity(escFalse, true, '4. esc: false');
return escFalse.close().then(function () {
checkVisiblity(escTrue, true, '5. esc: true');
checkVisiblity(escFalse, false, '6. esc: false');
simulateEvent(document, 'keyup', { keyCode: Ply.keys.esc });
return sleep(function () {
checkVisiblity(escTrue, false, '7. esc: true');
checkVisiblity(escFalse, false, '8. esc: false');
}, 50);
});
});
});
});
promiseTest('closeByOverlay', function () {
function test(msg, state, callback) {
var layer = new Ply({ flags: { closeByOverlay: state }, effect: { duration: 1 } });
return layer.open().then(function () {
checkVisiblity(layer, true, msg);
simulateEvent(layer.overlayEl, 'click');
return sleep(function () {
callback(layer, msg);
}, 50);
});
}
return test('closeByOverlay: true', true, function (layer, msg) {
checkVisiblity(layer, false, msg);
return test('closeByOverlay: false', false, function (layer, msg) {
checkVisiblity(layer, true, msg);
return layer.close();
});
})
});
promiseTest('swap', function () {
return new Ply({ el: 1, effect: 'none:1' }).open().then(function (layer) {
return layer.swap({ el: 2 }).then(function () {
equal(layer.contentEl.innerHTML, 2);
return layer.close().then(function () {
return layer.swap({ el: 3 }, 'none:2').then(function () {
equal(layer.contentEl.innerHTML, 3);
});
});
});
});
});
promiseTest('destory', function () {
return new Ply({ effect: 'none:1' }).open().then(function (layer) {
checkVisiblity(layer, true, '#1');
layer.destroy();
checkVisiblity(layer, false, '#2');
});
});
promiseTest('on/off', function () {
var log = [],
logMe = function (prefix, evt, el) {
log.push(prefix + ':' + el.tagName + '.' + evt.type + '->' + el.getAttribute('data-ply'));
},
barHandle = function (evt, el) {
log.pop();
logMe('bar', evt, el);
}
;
return new Ply({ el: '<i data-ply="foo"><em>foo</em></i><b data-ply="bar"><em>bar</em></b>' }).open().then(function (layer) {
layer.on('click', function (evt, el) {
logMe('layer', evt, el);
});
layer.on('click', 'foo', function (evt, el) {
logMe('foo', evt, el);
});
layer.on('click', 'bar', barHandle);
simulateEvent(layer.contentEl.getElementsByTagName('em')[0], 'click');
simulateEvent(layer.contentEl.getElementsByTagName('em')[1], 'click');
layer.off('click', barHandle);
simulateEvent(layer.contentEl.getElementsByTagName('em')[1], 'click');
layer.off('click', 'bar', barHandle);
simulateEvent(layer.contentEl.getElementsByTagName('em')[1], 'click');
equal(log.join('\n'), [
'layer:DIV.click->layer',
'foo:I.click->foo',
'bar:B.click->bar',
'bar:B.click->bar',
'layer:DIV.click->layer'
].join('\n'));
return layer.close();
});
});
// if (/state=2/.test(location)) {
// promiseTest('Promise', function () {
// expect(0);
//
// return Ply.promise(function (resolve) {
// window.resolveTest = function () {
// resolve();
// };
// document.write('<iframe src="?state=2"></iframe>');
// });
// });
// }
})();

131
tests/Ply.ui.tests.js

@ -0,0 +1,131 @@
(function (Ply) {
module('Ply.ui');
promiseTest('dialog("unknown")', function () {
setTimeout(function () {
simulateEvent(Ply.stack.last.overlayEl, 'click');
}, 50);
return Ply.dialog('unknown').then(function (ui) {
equal(ui.by, 'overlay', 'ui.by');
equal(ui.state, false, 'ui.state');
});
});
promiseTest('dialog("alert")', function () {
setTimeout(function () {
var el = Ply.stack.last.wrapEl;
simulateEvent(el.getElementsByTagName('button')[0], 'click');
}, 50);
return Ply.dialog('alert', { effect: 'none:1' }, 'msg').then(function (ui) {
equal(ui.by, 'submit', 'ui.by');
equal(ui.state, true, 'ui.state');
});
});
promiseTest('dialog("confirm")', function () {
setTimeout(function () {
var el = Ply.stack.last.wrapEl;
simulateEvent(el.getElementsByTagName('button')[1], 'click');
}, 50);
return Ply.dialog('confirm', { effect: 'none:1' }, {
title: "???",
text: "!!!"
}).then(function (ui) {
equal(ui.by, 'cancel', 'ui.by');
equal(ui.state, false, 'ui.state');
});
});
promiseTest('dialog("prompt")', function () {
setTimeout(function () {
var el = Ply.stack.last.wrapEl;
Ply.stack.last.context.val('email', 'xx@yy.zz');
simulateEvent(el.getElementsByTagName('button')[0], 'click');
}, 50);
return Ply.dialog('prompt', { effect: 'none:1' }, {
title: "???",
form: { email: "E-mail" }
}).then(function (ui) {
equal(ui.by, 'submit', 'ui.by');
equal(ui.state, true, 'ui.state');
equal(ui.context.val('email'), 'xx@yy.zz');
});
});
promiseTest('dialog("confirm") with YES/NO', function () {
setTimeout(function () {
var el = Ply.stack.last.wrapEl;
simulateEvent(el.getElementsByTagName('button')[0], 'click');
}, 50);
return Ply.dialog("confirm", { effect: 'none:1' }, { ok: 'YES', cancel: 'NO' }).then(function (ui) {
equal(ui.layer.layerEl.getElementsByTagName('button')[0].innerHTML, 'YES', 'ok');
equal(ui.layer.layerEl.getElementsByTagName('button')[1].innerHTML, 'NO', 'cancel');
});
});
promiseTest('dialog({ steps })', function () {
var log = [];
return Ply.dialog({
'foo': {
data: { text: 'foo' },
options: { effect: 'none:1' },
prepare: function (data) {
data.text += '!';
},
next: 'baz'
},
'bar': {
data: { text: 'bar' }
},
'baz': {
ui: 'confirm',
data: { text: 'baz' },
back: 'bar'
}
}, {
progress: function (ui) {
log.push(ui.name + ':' + ui.state);
setTimeout(function () {
simulateEvent(
ui.layer.layerEl.getElementsByTagName('button')[1]
|| ui.layer.layerEl.getElementsByTagName('button')[0],
'click');
}, 10);
}
}).then(function () {
equal(log.join('\n'), [
'foo:undefined',
'baz:true',
'bar:false'
].join('\n'));
});
});
promiseTest('factory.use()', function () {
Ply.factory('test', function (options, data, resolve) {
Ply.factory.use('alert', options, {
text: '!?',
ok: 'Y'
}, resolve);
});
return Ply.create("test").then(function (layer) {
equal(layer.context.getEl('el').innerHTML, '!?');
equal(layer.layerEl.getElementsByTagName('button')[0].innerHTML, 'Y');
});
});
})(Ply);

133
tests/index.html

@ -0,0 +1,133 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/html">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Ply :: Tests</title>
<!--script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script src="http://code.jquery.com/qunit/qunit-git.js"></script>
<link href="http://code.jquery.com/qunit/qunit-git.css" rel="stylesheet"/-->
<script>
if (!window.jQuery) {
document.write('<script src="http://local.git/js/jquery.dev.js"><' + '/script>');
document.write('<script src="http://local.git/js/qunit/qunit.js"><' + '/script>');
document.write('<link href="http://local.git/js/qunit/qunit.css" rel="stylesheet"/>');
}
if (/state=2/.test(location)) {
// document.write('<script src="http://local.git/JSSDK/Promise/Promise.js"><' + '/script>');
}
</script>
<script>
(function () {
var defaultOptions = {
x: 0,
y: 0,
button: 0,
ctrlKey: false,
altKey: false,
shiftKey: false,
metaKey: false,
bubbles: true,
cancelable: true
};
var eventMatchers = {
'HTMLEvents': /^(?:load|unload|abort|error|select|change|submit|reset|focus|input|keydown|keyup|blur|resize|scroll)$/,
'MouseEvents': /^(?:click|dblclick|mouse(?:down|up|over|move|out))$/
};
window.isPhantomJS = /phantomjs/i.test(navigator.userAgent);
window.simulateEvent = function (element, eventName, opts) {
var options = $.extend({}, defaultOptions, opts || {});
var oEvent, eventType = null;
for (var name in eventMatchers) {
if (eventMatchers[name].test(eventName)) {
eventType = name;
break;
}
}
if (!eventType) {
throw new SyntaxError('Only HTMLEvents and MouseEvents interfaces are supported');
}
if (document.createEvent) {
oEvent = document.createEvent(eventType);
if (eventType == 'HTMLEvents') {
oEvent.initEvent(eventName, options.bubbles, options.cancelable);
}
else {
oEvent.initMouseEvent(eventName, options.bubbles, options.cancelable, window,
options.button, options.x, options.y, options.x, options.y,
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, element);
}
$.extend(oEvent, options);
element.dispatchEvent(oEvent);
}
else {
options.clientX = options.x;
options.clientY = options.y;
var evt = document.createEventObject();
oEvent = $.extend(evt, options);
element.fireEvent('on' + eventName, oEvent);
}
return element;
};
window.promiseTest = function (name, promise) {
asyncTest(name, function () {
promise().then(function () {
start();
}, function (err) {
equal([err, err.stack], null, 'fail');
start();
});
});
};
})();
</script>
</head>
</body>
<h1 id="qunit-header">Ply :: tests</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="playground"></div>
<!-- lib:css -->
<link href="../ply.css" rel="stylesheet"/>
<!-- lib:src -->
<script src="../Ply.js"></script>
<script src="../Ply.ui.js"></script>
<!-- lib:tests -->
<script src="./Ply.tests.js"></script>
<script src="./Ply.dom.tests.js"></script>
<script src="./Ply.effects.tests.js"></script>
<script src="./Ply.ui.tests.js"></script>
<script>
if (parent.resolveTest) {
test('end', function () {
expect(0);
parent.resolveTest();
});
}
</script>
</body>
</html>
Loading…
Cancel
Save