Merge pull request #1 from lyweiwei/master

Check in the new backbone-toolbar
This commit is contained in:
Wei Wei 2016-08-24 12:10:52 +08:00 коммит произвёл GitHub
Родитель 5d8aeba2f1 1ad2775459
Коммит b89cfe0c07
76 изменённых файлов: 1089 добавлений и 1347 удалений

11
.editorconfig Normal file
Просмотреть файл

@ -0,0 +1,11 @@
root = true
[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

8
.eslintignore Normal file
Просмотреть файл

@ -0,0 +1,8 @@
# npm packages
node_modules
# code coverage
coverage
# output folder
dist

14
.eslintrc.yaml Normal file
Просмотреть файл

@ -0,0 +1,14 @@
---
"extends": "xo-space"
"env":
"mocha": true
"amd": true
"browser": true
"rules":
"comma-dangle":
- 2
- "always-multiline"
"object-curly-spacing":
- 2
- "always"
"linebreak-style": 0

16
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,16 @@
# npm packages
node_modules
# code coverage
coverage
# output folder
dist
# npm logs
node-debug.log
npm-debug.log
# generated code for example pages
examples/requirejs/require.config.js
examples/webpack/dist

6
.travis.yml Normal file
Просмотреть файл

@ -0,0 +1,6 @@
language: node_js
node_js:
- v5
- v4
- '0.12'
- '0.10'

Просмотреть файл

@ -1,2 +1,36 @@
# backbone-toolbar
[![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url] [![Coverage percentage][coveralls-image]][coveralls-url]
A Backbone based toolbar implementation.
## Usage
```bash
npm install --save backbone-toolbar
```
In your JavaScript code
### As AMD
```javascript
require(['backbone-toolbar'], function (backboneToolbar) {
// use the backboneToolbar
});
```
### As CMD
```javascript
var backboneToolbar = require('backbone-toolbar');
// use the backboneToolbar
```
[npm-image]: https://badge.fury.io/js/backbone-toolbar.svg
[npm-url]: https://npmjs.org/package/backbone-toolbar
[travis-image]: https://travis-ci.org/Microsoft/backbone-toolbar.svg?branch=master
[travis-url]: https://travis-ci.org/Microsoft/backbone-toolbar
[daviddm-image]: https://david-dm.org/Microsoft/backbone-toolbar.svg?theme=shields.io
[daviddm-url]: https://david-dm.org/Microsoft/backbone-toolbar
[coveralls-image]: https://coveralls.io/repos/Microsoft/backbone-toolbar/badge.svg
[coveralls-url]: https://coveralls.io/r/Microsoft/backbone-toolbar

Просмотреть файл

@ -0,0 +1,3 @@
{
"template": "./index.jade"
}

7
demos/default/index.jade Normal file
Просмотреть файл

@ -0,0 +1,7 @@
doctype html
html(lang="en")
head
title= title
body
.toolbar-container
script(type="application/javascript", src=bundle)

60
demos/default/index.js Normal file
Просмотреть файл

@ -0,0 +1,60 @@
import { ToolbarView } from '../../js';
import 'bootstrap-webpack';
window.toolbar = new ToolbarView({
el: '.toolbar-container',
id: 'demo-toolbar',
items: [{
type: 'button',
classes: ['btn', 'btn-primary'],
id: 'button-1st',
text: 'A button',
onClick: () => console.log('click button-1st'),
}, {
type: 'button',
iconLeft: 'glyphicon-th-large',
text: 'The 2nd Button',
id: 'button-2nd',
onClick: () => console.log('click button-2nd'),
}, {
type: 'button',
text: 'The 3rd Button',
iconRight: 'glyphicon-th-list',
id: 'button-3rd',
onClick: () => console.log('click button-3rd'),
}, {
type: 'dropdown',
button: {
text: 'Dropdown',
},
menu: {
items: [{
text: 'The 1st DropdownItem',
id: 'dropdown-item-1st',
onClick: () => console.log('click dropdown-item-1st'),
}, {
type: 'dropdown-divider',
}, {
type: 'dropdown-header',
text: 'Hello world!',
}, {
text: 'The 2nd DropdownItem',
id: 'dropdown-item-2nd',
onClick: () => console.log('click dropdown-item-2nd'),
}, {
type: 'dropdown-divider',
}, {
type: 'dropdown-radio-group',
id: 'dropdown-radio-group-1st',
onSelect: value => console.log(`select ${value}`),
onRemove: value => console.log(`remove ${value}`),
title: 'A simple radio group',
items: [
{ text: 'foo', value: 'foo' },
{ text: 'bar', value: 'bar' },
],
}],
},
}],
}).render();

Просмотреть файл

@ -0,0 +1,10 @@
var expect = require('chai').expect;
/* global browser */
/* eslint no-unused-expressions: 0 */
describe('webdriver.io page', function () {
it('should render the head line correctly', function () {
var headLine = browser.element('h1');
expect(headLine).to.be.exist;
});
});

16
demos/webpack.config.js Normal file
Просмотреть файл

@ -0,0 +1,16 @@
var webpack = require('webpack');
var webpackConfig = require('../webpack.config');
webpackConfig.plugins = webpackConfig.plugins || [];
webpackConfig.plugins.push(new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }));
webpackConfig.module.loaders = webpackConfig.module.loaders.concat([
{ test: /bootstrap[\\\/]js[\\\/]/, loader: 'imports?jQuery=jquery' },
{ test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/font-woff' },
{ test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/octet-stream' },
{ test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file' },
{ test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=image/svg+xml' },
]);
module.exports = webpackConfig;

Просмотреть файл

@ -1,182 +0,0 @@
.grid-toolbar {
background-color: #fff;
font-size: 12px;
}
.grid-menu-container {
vertical-align: middle;
padding: 7px 10px;
display: inline-block;
background: none;
}
.grid-menu {
padding: 0;
border: none;
background: none;
cursor: pointer;
font-size: 12px;
min-width: 0;
height: auto;
line-height: normal;
outline: 0;
}
.grid-menu-container:hover, .grid-groupmenu-container:hover {
background-color: #e5e5e5;
cursor: pointer;
}
.spritedimage {
display: inline-block;
margin: 0 5px;
position: relative;
}
.icon-arrowdown-normal {
background: transparent url('./sharedUI_14.png') no-repeat scroll -2px -860px;
width: 9px;
height: 5px;
overflow: hidden;
}
.dropdown-menu {
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
display: none;
float: left;
min-width: 160px;
padding: 0px;
margin: 2px 0 0;
list-style: none;
font-size: 12px;
background-color: #ffffff;
border: 2px solid #cccccc;
border-radius: 0px;
background-clip: padding-box;
}
.dropdown-menu .dropdown-header {
padding: 7px 10px;
font-weight: bold;
font-size: 12px;
cursor: auto;
}
.dropdown-header {
display: block;
padding: 3px 20px;
font-size: 11px;
line-height: 1.45833333;
color: #666666;
}
.dropdown-menu > li > .anchor:hover,
.dropdown-menu > li > .anchor:focus {
text-decoration: none;
color: #262626;
background-color: #e5e5e5;
}
.dropdown-menu > li > .anchor {
display: block;
padding: 7px 10px;
clear: both;
font-family: Arial;
font-size: 12px;
font-weight: normal;
line-height: 1.45833333;
color: #333333;
white-space: nowrap;
}
.dropdown-menu > li .primary {
padding-right: 80px;
}
.dropdown-menu > li .secondary {
font-size: 10.2px;
line-height: 17.5px;
position: absolute;
right: 10px;
}
.dropdown-menu .divider {
height: 1px;
margin: 7.5px 5px;
overflow: hidden;
background-color: #e5e5e5;
margin: 3px 5px;
cursor: auto;
clear: both;
}
.dropdown-menu > li .glyphicon-offset-left5 {
margin-right: 5px;
}
.dropdown-menu > li .glyphicon {
width: 12px;
}
.glyphicon {
position: relative;
top: 1px;
display: inline-block;
font-family: 'Glyphicons Halflings';
font-style: normal;
font-weight: normal;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
button.filter_search_icon_small {
margin: 0;
cursor: pointer;
border: none;
background-color: transparent;
background-repeat: no-repeat;
min-width: 0;
padding: 0;
line-height: normal;
}
.filter_search_icon_small {
background: transparent url('./sharedUI_14.png') no-repeat scroll -2px -443px;
width: 20px;
height: 20px;
overflow: hidden;
}
button.filter_search_icon_small {
vertical-align: middle;
}
input, button, select, textarea {
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
.wunderbartreeclose, .grid-expand-icon, .expand .alert-arrow-icon {
background: transparent url('./sharedUI_18.png') no-repeat scroll -2px -1486px;
width: 5px;
height: 10px;
overflow: hidden;
cursor: pointer;
}
.toolbar-icon.toolbar-icon-left {
margin-left: 0px;
margin-right: 4px;
}
.icon-export-normal {
background: transparent url('./sharedUI_18.png') no-repeat scroll -2px -983px;
width: 9px;
height: 12px;
overflow: hidden;
}

Просмотреть файл

@ -1,180 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<link href="../../../.out/lib/bootstrap/dist/css/bootstrap.css" rel="stylesheet" media="screen">
<link href="http://localhost:8004/StaticResourcesWeb/Application/Styles/theme_next.css" rel="stylesheet" media="screen">
<link href="http://localhost:8004/StaticResourcesWeb/Application/Styles/controls_next.css" rel="stylesheet" media="screen">
<link href="../style/grid-toolbar.css" rel="stylesheet" media="screen">
<script type="text/javascript" src="../../../.out/lib/requirejs-2.1.14/require.js"></script>
<script type="text/javascript" src="../../require.config.js"></script>
</head>
<body>
<div class='container'>
<div class='row'>
<div class="panel panel-info">
<div class="panel-heading">Commands you can run in the console</div>
<div class="panel-body">
<pre>
</pre>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">Notes</div>
<div class="panel-body">
<ul>
<li>Column 'ContactName' is sortable</li>
<li>Shift Columns right and left</li>
<li>Use Pager to navigate OData Source</li>
</ul>
</div>
</div>
</div>
<div class='row'>
<div id='pagination_host_a'></div>
<div id='grid_host_a'></div>
</div>
</div>
<script type="text/javascript">
window.BINGADS_DEBUG = true;
require.config({
'baseUrl': '../../../'
});
require(['lib/squire', 'component/auto-config'], function(Squire){
var injector = new Squire();
injector
.mock({
'component/config/index': { get : function(){ return 'en-us'; } },
'data/i18n/component/grid-toolbar/en-us': {
'GridToolbar_FilterBar_Title': 'Filter',
'GridToolbar_FilterBar_Edit': 'Edit',
'GridToolbar_FilterBar_Remove': 'Remove'
}
})
.require([
'lib/underscore',
'lib/jquery',
'lib/backbone',
'component/i18n/index',
'component/grid-toolbar/button',
'component/grid-toolbar/dropdown',
'component/grid-toolbar/buttonMenuItem',
'component/grid-toolbar/dividerMenuItem',
'component/grid-toolbar/headerMenuItem',
'component/grid-toolbar/radioGroupMenuItem',
'component/grid-toolbar/radioMenuItem',
'component/grid-toolbar/subMenuItem',
'component/grid-toolbar/filterInput',
'component/grid-toolbar/gridToolbarItemContainer',
'component/grid-toolbar/gridToolbar',
'component/grid-toolbar/filterBar',
],
function(_, $, Backbone, i18nModel, Button, Dropdown, ButtonMenuItem, DividerMenuItem, HeaderMenuItem,
RadioGroupMenuItem, RadioMenuItem, SubMenuItem, FilterInput, GridToolbarItemContainer,
GridToolbar, FilterBar) {
_.templateSettings = {
interpolate: /\{\{(.+?)\}\}/g
};
$(function(){
var columnMenu = new Dropdown({
title: "Columns",
menuItems: [
new ButtonMenuItem({text: 'Modify Columns'}),
new DividerMenuItem(),
new HeaderMenuItem({text: 'Apply saved columns'}),
new RadioGroupMenuItem({items: [
new RadioMenuItem({text: 'Custom'})
]})
]
});
var helloMenu = new Dropdown({
title: "Hello World",
menuItems: [
new HeaderMenuItem({text: "Header"}),
new ButtonMenuItem({text: "hello", linkText: 'Remove'}),
new DividerMenuItem(),
new ButtonMenuItem({text: "world"}),
new RadioGroupMenuItem({items: [
new RadioMenuItem({text: "radio1", linkText: "Remove"}),
new RadioMenuItem({text: "radio2", linkText: "Remove"})
]}),
new DividerMenuItem(),
new RadioGroupMenuItem({items: [
new RadioMenuItem({text: "radio11", linkText: "Change"}),
new RadioMenuItem({text: "radio22", linkText: "Change"})
]})
]
});
function getGroupItem(dropdown, index) {
var groupItems = dropdown.getMenuItems().filter(function(item) {
return item.isGroup;
});
return groupItems[index];
}
getGroupItem(helloMenu, 0).on('click:link', function(button, item) {
button.removeItem(item);
});
getGroupItem(helloMenu, 1).on('click:link', function(button, item) {
item.setText('Changed!!!');
});
helloMenu.on('click:link', function(button, item) {
if (item.getGroup == null) {
button.removeMenuItem(item);
}
});
var filterMenu = new Dropdown({
title: 'Filter',
menuItems: [
new ButtonMenuItem({text: 'Create filter'}),
new DividerMenuItem(),
new HeaderMenuItem({text: 'Apply saved filters'}),
new SubMenuItem({text: 'The Sub MenuItem', children: [
new ButtonMenuItem({text: 'Good Study'}),
new ButtonMenuItem({text: 'Hello World'}),
new ButtonMenuItem({text: 'Nimei HAHAH'}),
]}),
new SubMenuItem({text: 'New SubItem', children: [
new ButtonMenuItem({text: 'Change current bids'}),
new ButtonMenuItem({text: 'Pass ad group'}),
new ButtonMenuItem({text: 'Enable ad groups'})
]})
]
});
var filterInput = new FilterInput({placeholder: "Hello World"});
filterInput.on('change', function(button, filterVal) {
button.setValue('Apply!');
});
var filterContainer = new GridToolbarItemContainer({items: [filterMenu, filterInput]});
var buttonMenu = new Button({text: 'Click Me', leftIconClass: 'icon-export-normal'});
buttonMenu.on('click', function() {
alert('The button is clicked!');
});
var toolbar = new GridToolbar({items: [buttonMenu, helloMenu, columnMenu, filterContainer]});
var filterBar = new FilterBar({columnName: 'Account Name', filterOperator: 'Contains', filterValue: 'ss'});
filterBar.on('edit', function() {
alert('Click the edit button');
}),
filterBar.on('remove', function() {
alert('Click the remove button');
});
$('body').append(toolbar.render().$el);
$('body').append(filterBar.render().$el);
});
});
});
</script>
</body>
</html>

Двоичные данные
examples/sharedUI_14.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 56 KiB

Двоичные данные
examples/sharedUI_18.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 83 KiB

169
gulpfile.js Normal file
Просмотреть файл

@ -0,0 +1,169 @@
var fs = require('fs');
var os = require('os');
var http = require('http');
var path = require('path');
var childProcess = require('child_process');
var resolve = require('resolve');
var gulp = require('gulp');
var gutil = require('gulp-util');
var eslint = require('gulp-eslint');
var democase = require('gulp-democase');
var excludeGitignore = require('gulp-exclude-gitignore');
var webpack = require('webpack');
var del = require('del');
// coveralls
var coveralls = require('gulp-coveralls');
// coveralls-end
var jsdoc = require('gulp-jsdoc3');
function webpackBuild(configFilePath) {
return function (cb) {
webpack(require(configFilePath), function (err, stats) {
gutil.log(stats.toString({ colors: true }));
cb(err || stats.hasErrors() && new Error('webpack compile error'));
});
};
}
function getSeleniumFilePath() {
var SELENIUM_NAME = 'selenium-server-standalone-2.53.0.jar';
return path.resolve(os.tmpdir(), SELENIUM_NAME);
}
gulp.task('download-selenium', function (cb) {
var filePath = getSeleniumFilePath();
fs.stat(filePath, function (err) {
if (!err) {
return cb(null);
}
var file = fs.createWriteStream(filePath);
var URL = 'http://selenium-release.storage.googleapis.com/2.53/selenium-server-standalone-2.53.0.jar';
http.get(URL, function (response) {
response.pipe(file);
});
file.on('error', function (err) {
fs.unlinkSync(filePath);
cb(err);
});
file.on('finish', cb);
});
});
function startSeleniumServer() {
var filePath = getSeleniumFilePath();
return childProcess.spawn('java', ['-jar', filePath], {
stdio: 'inherit',
env: { path: path.join(__dirname, 'node_modules', '.bin') },
});
}
function testWithKarmaCmd(handler) {
var karmaCmd = path.resolve('./node_modules/.bin/karma');
if (process.platform === 'win32') {
karmaCmd += '.cmd';
}
childProcess.spawn(karmaCmd, [
'start',
'--single-run',
], { stdio: 'inherit' }).on('close', handler);
}
/*
function testWithKarmaAPI(handler) {
var Server = require('karma').Server;
new Server({
configFile: path.join(__dirname, 'karma.conf.js'),
singleRun: true,
}, handler).start();
}
*/
gulp.task('test:unit', function (cb) {
var handler = function (code) {
if (code) {
cb(new Error('test failure'));
} else {
cb();
}
};
testWithKarmaCmd(handler);
});
// coveralls
gulp.task('coveralls', ['test'], function () {
if (!process.env.CI) {
return;
}
return gulp.src(path.join(__dirname, 'coverage/report-lcov/lcov.info'))
.pipe(coveralls());
});
// coveralls-end
gulp.task('static', function () {
return gulp.src(['js/**/*.js', 'demos/**/*.js', 'spec/**/*.js'])
.pipe(excludeGitignore())
.pipe(eslint())
.pipe(eslint.format())
.pipe(eslint.failAfterError());
});
gulp.task('docs', function (cb) {
gulp.src(['README.md', './src/**/*.js'], { read: false })
.pipe(jsdoc(require('./jsdoc.json'), cb));
});
gulp.task('webpack', webpackBuild('./webpack.config'));
gulp.task('demos', function () {
return gulp.src('./demos').pipe(democase());
});
gulp.task('test:demos', ['download-selenium'], function (done) {
var pathCli = path.resolve(path.dirname(resolve.sync('webdriverio', {
basedir: '.',
})), 'lib/cli');
var cpSelenium = null;
var cpWdio = null;
cpSelenium = startSeleniumServer().on('error', function () {
if (cpWdio) {
cpWdio.kill();
}
done(new Error('Failed to launch the selenium standalone server. Make sure you have JRE available'));
});
cpWdio = childProcess.fork(pathCli, [path.join(__dirname, 'wdio.conf.js')], {
env: { DEMOCASE_HTTP_PORT: 8081 },
}).on('close', function (code) {
cpSelenium.kill();
if (code) {
done(new Error('selenium test failue'));
}
done();
});
});
gulp.task('test', ['test:unit']);
gulp.task('prepublish', ['webpack']);
gulp.task('clean:test', function () {
return del(['coverage']);
});
gulp.task('clean:build', function () {
return del(['dist']);
});
gulp.task('clean', ['clean:build', 'clean:test']);
gulp.task('default', [
'static',
'webpack',
// coveralls
'coveralls',
// coveralls-end
]);

Просмотреть файл

@ -1,19 +0,0 @@
define([
'component/grid-toolbar/menuItem'
],
function(MenuItem) {
'use strict';
// the divider menu item of dropdown button
var DividerMenuItem = MenuItem.extend({
className: 'divider',
attributes: {
role: 'presentation'
}
});
return DividerMenuItem;
});

Просмотреть файл

@ -1,30 +0,0 @@
define([
'component/grid-toolbar/menuItem'
],
function(MenuItem) {
'use strict';
// the menu item of dropdown button
/*
The options can contain the following properties:
* text: the text of menu bar
*/
var HeaderMenuItem = MenuItem.extend({
tagName: 'li',
className: 'dropdown-header',
attributes: {
role: 'presentation'
},
render: function() {
var text = this.options ? this.options.text : '';
this.$el.text(text);
return this;
}
});
return HeaderMenuItem;
});

8
js/button-mixin.jade Normal file
Просмотреть файл

@ -0,0 +1,8 @@
mixin buttonMixin(options)
button&attributes(options.attributes)(class=options.classes, id=options.id, type='button', role='button', tabindex=options.tabindex)
if options.iconLeft
span.icon-left.glyphicon(class=options.iconLeft)
span.text=options.text
if options.iconRight
span.icon-right.glyphicon(class=options.iconRight)

14
js/button.jade Normal file
Просмотреть файл

@ -0,0 +1,14 @@
include ./button-mixin.jade
-
var options = {
classes: classes,
attributes: {},
id: id,
tabindex: tabindex,
iconLeft: iconLeft,
text: text,
iconRight: iconRight,
};
+buttonMixin(options)

Просмотреть файл

@ -1,44 +1,25 @@
define([
'lib/underscore',
'lib/backbone',
'component/grid-toolbar/template/button'
],
function(_, Backbone, buttonTmpl) {
'use strict';
import _ from 'underscore';
import buttonTemplate from './button.jade';
// the button toolbar menu.
/*
The options can contain the following properties:
* text: the text of menu bar
* leftIconClass: the left icon class of the menu
## events
* `click`: `function(MenuItem)` trigger when user click the toolbar menu.
*/
var Button = Backbone.View.extend({
className: 'grid-menu-container',
events: {
'click': '_onClick'
},
initialize: function(options) {
this.options = _.defaults(options || {}, {
text: '',
leftIconClass: null
});
},
_onClick: function(event) {
this.trigger('click', this);
},
render: function() {
this.$el.html(buttonTmpl(this.options));
return this;
}
export function renderButton(button) {
const options = _.defaults({}, button, {
classes: ['btn', 'btn-default'],
id: _.uniqueId('button-'),
text: '',
iconLeft: null,
iconRight: null,
tabindex: -1,
onClick: null,
});
const html = buttonTemplate(options);
const events = {};
const { id, onClick } = options;
if (_.isFunction(onClick)) {
events[`click button#${id}`] = onClick;
}
return { html, events };
}
return Button;
});

Просмотреть файл

@ -1,65 +0,0 @@
define([
'lib/underscore',
'component/grid-toolbar/menuItem',
'component/grid-toolbar/template/buttonMenuItem'
],
function(_, MenuItem, menuItem) {
'use strict';
// the menu item of dropdown button
/*
The options can contain the following properties:
* text: the text of menu bar
* linkText: the text that show on the right of text
## events
* `click`: `function(MenuItem)` trigger when user click the menu item.
* `click:link`: `function(MenuItem)` trigger when user click teh link item.
*/
var ButtonMenuItem = MenuItem.extend({
attributes: {
role: 'presentation'
},
events: {
'click' : '_onClick',
'click .secondary' : '_onClickLink'
},
initialize: function(options) {
this.options = _.defaults(options || {}, {
text: '',
linkText: ''
});
},
setText: function(text) {
if (this.options.linkText != null) {
this.$el.find('.primary').text(text);
} else {
this.$el.find('.anchor').text(text);
}
},
setLinkText: function(text) {
this.$el.find('.secondary').text(text);
},
_onClick: function(event) {
this.trigger('click', this);
},
_onClickLink: function(event) {
this.trigger('click:link', this);
event.stopPropagation();
},
render: function() {
this.$el.html(menuItem(this.options));
return this;
}
});
return ButtonMenuItem;
});

1
js/dropdown-divider.jade Normal file
Просмотреть файл

@ -0,0 +1 @@
li.divider(role='separator')

6
js/dropdown-divider.js Normal file
Просмотреть файл

@ -0,0 +1,6 @@
import dropdownDividerTemplate from './dropdown-divider.jade';
export function renderDropdownDivider() {
return { html: dropdownDividerTemplate() };
}

1
js/dropdown-header.jade Normal file
Просмотреть файл

@ -0,0 +1 @@
li.dropdown-header(role='presentation')=text

9
js/dropdown-header.js Normal file
Просмотреть файл

@ -0,0 +1,9 @@
import _ from 'underscore';
import dropdownHeaderTemplate from './dropdown-header.jade';
export function renderDropdownHeader(dropdownHeader) {
return {
html: dropdownHeaderTemplate(_.defaults(dropdownHeader, { text: '' })),
};
}

8
js/dropdown-item.jade Normal file
Просмотреть файл

@ -0,0 +1,8 @@
li.dropdown-item(role='presentation', class=classes, id=id)
a(role='menuitem')
if iconLeft
span.icon-left.glyphicon(class=iconLeft)
span.text=text
if iconRight
span.icon-right.glyphicon(class=iconRight)

25
js/dropdown-item.js Normal file
Просмотреть файл

@ -0,0 +1,25 @@
import _ from 'underscore';
import dropdownItemTemplate from './dropdown-item.jade';
export function renderDropdownItem(dropdownItem) {
const options = _.extend({
classes: [],
id: _.uniqueId('dropdown-item-'),
text: '',
iconLeft: null,
iconRight: null,
tabindex: -1,
onClick: null,
}, dropdownItem);
const html = dropdownItemTemplate(options);
const events = {};
const { id, onClick } = options;
if (_.isFunction(onClick)) {
events[`click .dropdown-item#${id}`] = onClick;
}
return { html, events };
}

Просмотреть файл

@ -0,0 +1,18 @@
li.dropdown-radio-group(class=classes, id=id)
ul.dropdown-menu.dropdown-menu-group
li.dropdown-header(role='presentation')=title
each item in items
li.dropdown-radio-item(role='presentation', data-value=item.value)
a(role='menuitem')
if item.value === value
span.glyphicon.glyphicon-ok.selection.selection-selected
else
span.glyphicon.glyphicon-ok.selection
span.text=item.text
if !item.locked
span.remove
if removeText
span.pull-right.remove-text=removeText
if removeIcon
span.pull-right.glyphicon.remove-icon(class=removeIcon)

Просмотреть файл

@ -0,0 +1,50 @@
import $ from 'jquery';
import _ from 'underscore';
import dropdownRadioGroupTemplate from './dropdown-radio-group.jade';
import './dropdown-radio-group.less';
function normalizeItem(item) {
const dropdownRadioGroup = _.extend({
classes: [],
title: '',
items: [],
removeText: null,
removeIcon: null,
}, item);
if (!dropdownRadioGroup.removeIcon && !dropdownRadioGroup.removeText) {
dropdownRadioGroup.removeIcon = 'glyphicon-remove';
}
return dropdownRadioGroup;
}
export function renderDropdownRadioGroup(item, renderItem) {
const dropdownRadioGroup = normalizeItem(item);
const html = dropdownRadioGroupTemplate(dropdownRadioGroup);
const {
id,
onSelect = _.noop,
onRemove = _.noop,
} = dropdownRadioGroup;
function onClick(e) {
const $el = $(e.target);
const $li = $el.closest('li');
const $remove = $el.closest('.remove', $li.get(0));
const value = $li.attr('data-value');
if ($remove.length > 0) {
onRemove(value);
} else {
this.update({ id, value });
onSelect(value);
}
}
const events = {
[`click #${id} .dropdown-radio-item`]: onClick,
};
return { html, events };
}

Просмотреть файл

@ -0,0 +1,23 @@
.toolbar {
.dropdown-radio-group {
> .dropdown-menu.dropdown-menu-group {
display: block;
position: static;
left: 0;
width: 100%;
margin: 0;
padding: 0;
border: none;
box-shadow: none;
span.selection {
visibility: hidden;
margin-right: 5px;
}
span.selection.selection-selected {
visibility: visible;
}
}
}
}

7
js/dropdown.jade Normal file
Просмотреть файл

@ -0,0 +1,7 @@
include ./button-mixin.jade
.dropdown(class=classes, id=id)
+buttonMixin(button)
ul.dropdown-menu(role='menu', aria-labeledby=button.id, class=classes, id=id)
each item in menu.items
!= item.html

Просмотреть файл

@ -1,113 +1,46 @@
define([
'lib/underscore',
'lib/backbone',
'component/grid-toolbar/template/dropdown',
'lib/bootstrap'
],
function(_, Backbone, buttonTmpl) {
'use strict';
import _ from 'underscore';
import dropdownTemplate from './dropdown.jade';
// The toolbar view implement the grid toolbar such as filter or select action
export function renderDropdown(dropdown, renderItem) {
const { button = {}, menu = {} } = dropdown;
/*
## props
* text: `string` the button text
* isShowRightIcon: `boolean` if the right icon needs to show.
* leftIconClass/rightIconClass: `string` the class name of button's left/right icon.
* menuItems: `Array` the menu items
## events
* `click:item`: ``function(Dropdown, MenuItem)``
* `click:link`: `function(Dropdown, MenuItem)`
*/
var Dropdown = Backbone.View.extend({
className: 'grid-groupmenu-container dropdown',
initialize: function(options) {
this.options = _.defaults(options || {}, {
isShowRightIcon: true,
menuItems: []
});
this.menuItems = this.options.menuItems.slice(0);
_.each(this.menuItems, this._initMenuItem.bind(this));
},
getMenuItem: function(index) {
return this.menuItems[index];
},
getMenuItems: function() {
return this.menuItems;
},
// Add one menu item(Backbone.View instance) into the menu items.
pushMenuItem: function(item) {
this.menuItems.push(item);
this._initMenuItem(item);
this._renderMenuItem(item);
},
pushMenuItems: function(items) {
if (!_.isArray(items)) {
items = [items];
}
_.each(items, this.pushMenuItem, this);
},
removeMenuItem: function(item) {
if (item.getGroup) {
return this.removeRadioItem(item);
}
var index = this.menuItems.indexOf(item);
if (index !== -1) {
this.menuItems.splice(index, 1);
}
item.isGroup ? item.remove() : item.$el.remove();
},
removeMenuItems: function(items) {
if (!_.isArray(items)) items = [items];
_.each(items, this.removeMenuItem, this);
},
removeRadioItem: function(item) {
var groupItem = item.getGroup && item.getGroup();
groupItem && groupItem.removeItem(item);
},
_applyItemEvents: function(item) {
item.on('click', function(item) {
this.trigger('click:item', this, item);
}, this);
item.on('click:link', function(item) {
this.trigger('click:link', this, item);
}, this);
},
_initMenuItem: function(item) {
if (item.isGroup) {
_.each(item.items, this._applyItemEvents, this);
} else {
this._applyItemEvents(item);
}
},
_renderMenuItem: function(item) {
this.$itemList.append(item.render().$el);
},
render: function() {
this.$el.html(buttonTmpl(this.options));
this.$itemList = this.$el.children('.dropdown-menu');
// Add all the menu items into the button
_.each(this.menuItems, this._renderMenuItem, this);
return this;
}
const events = {};
const options = _.defaults({
button: _.defaults({
tabindex: dropdown.tabindex,
attributes: _.defaults({
'data-toggle': 'dropdown',
'aria-haspopup': 'true',
'aria-expanded': 'false',
}, button.attributes),
classes: _.union(button.classes || [
'btn',
'btn-default',
], [
'dropdown-toggle',
]),
}, button, {
id: _.uniqueId('dropdown-button-'),
text: '',
iconLeft: null,
iconRight: 'glyphicon-triangle-bottom',
}),
menu: _.defaults({
items: _.map(menu.items, (item, index) => ({
html: renderItem(_.defaults({
tabindex: index === 0 ? 0 : -1,
}, item, { type: 'dropdown-item' }), events),
})),
}, menu, {
classes: [],
id: _.uniqueId('menu-'),
}),
}, dropdown, {
classes: [],
id: _.uniqueId('dropdown-'),
});
const html = dropdownTemplate(options);
return { html, events };
}
return Dropdown;
});

Просмотреть файл

@ -1,53 +0,0 @@
define([
'lib/underscore',
'lib/backbone',
'component/grid-toolbar/template/filterBar',
'$/i18n!component/grid-toolbar'
],
function(_, Backbone, filterBarTmpl, i18n) {
'use strict';
// the base class of menu item
/**
## Options:
*columnName: the column name that want to apply to quick filter
*filterOperator: the quick filter operator, such as 'Contains'
*filterValue: the quick filter value
*i18n: i18n module that to localize the string
## Events
*`edit`: triggered when the Edit button is clicked
*`remove`: triggered when the Remove button is clicked
*/
var FilterBar = Backbone.View.extend({
className: 'filter-bar',
events: {
'click a.editFilterLink' : '_onClickEdit',
'click a.removeFilterLink' : '_onClickRemove'
},
initialize: function(options) {
this.options = _.extend({
title: i18n.get('GridToolbar_FilterBar_Title'),
editText: i18n.get('GridToolbar_FilterBar_Edit'),
removeText: i18n.get('GridToolbar_FilterBar_Remove')
}, options || {});
},
_onClickEdit: function(event) {
this.trigger('edit', this);
},
_onClickRemove: function(event) {
this.trigger('remove', this);
},
render: function() {
this.$el.html(filterBarTmpl(this.options));
return this;
},
});
return FilterBar;
});

Просмотреть файл

@ -1,55 +0,0 @@
define([
'lib/underscore',
'lib/backbone',
'component/grid-toolbar/template/filterInput'
],
function(_, Backbone, buttonTmpl) {
'use strict';
/*
## props
* placeholder: `string` the placeholder for the text input.
## events
* `change`: `function(FilterInput, String)` when the button is clicked.
*/
var FilterInput = Backbone.View.extend({
className: 'grid-filter-container',
events: {
'click button': '_onClickButton',
'keypress': '_onKeypress',
},
setValue: function(val) {
this.$input.val(val);
},
getValue: function() {
return this.$input.val();
},
_onClickButton: function(event) {
this.trigger('change', this, this.$input.val());
},
_onKeypress: function(event) {
var RETURN_CODE = 13;
if (event.which === RETURN_CODE) {
this.trigger('change', this, this.$input.val());
}
},
render: function() {
var props = _.defaults(this.options || {}, {
placeholder: ''
});
this.$el.html(buttonTmpl(props));
this.$input = this.$('input');
return this;
}
});
return FilterInput;
});

Просмотреть файл

@ -1,30 +0,0 @@
define([
'lib/underscore',
'lib/backbone'
],
function(_, Backbone) {
'use strict';
// the container for the menu buttons
/*
## props
* items: `Array` the list of toolbar menus.
*/
var GridToolbar = Backbone.View.extend({
className: 'grid-toolbar',
initialize: function(options) {
this.items = (options || {}).items || [];
},
render: function() {
_.each(this.items, function(item) {
this.$el.append(item.render().$el);
}, this);
return this;
}
});
return GridToolbar;
});

Просмотреть файл

@ -1,31 +0,0 @@
define([
'lib/underscore',
'lib/backbone'
],
function(_, Backbone) {
'use strict';
// the container for the menu buttons
/*
## props
* items: `Array` the list of toolbar menus.
*/
var GridToolbarItemContainer = Backbone.View.extend({
className: 'grid-toolbar-item-container',
initialize: function(options) {
this.items = (options || {}).items || [];
},
render: function() {
_.each(this.items, function(item) {
this.$el.append(item.render().$el);
}, this);
return this;
}
});
return GridToolbarItemContainer;
});

20
js/index.js Normal file
Просмотреть файл

@ -0,0 +1,20 @@
import { register } from './item-register.js';
import { renderToolbar } from './toolbar.js';
import { renderButton } from './button.js';
import { renderDropdown } from './dropdown.js';
import { renderDropdownItem } from './dropdown-item.js';
import { renderDropdownHeader } from './dropdown-header.js';
import { renderDropdownDivider } from './dropdown-divider.js';
import { renderDropdownRadioGroup } from './dropdown-radio-group.js';
register('toolbar', renderToolbar);
register('button', renderButton);
register('dropdown', renderDropdown);
register('dropdown-item', renderDropdownItem);
register('dropdown-header', renderDropdownHeader);
register('dropdown-divider', renderDropdownDivider);
register('dropdown-radio-group', renderDropdownRadioGroup);
export { register } from './item-register.js';
export { ToolbarView } from './toolbar-view.js';

24
js/item-register.js Normal file
Просмотреть файл

@ -0,0 +1,24 @@
import _ from 'underscore';
const renderers = {};
export function register(name, builder) {
if (_.has(renderers, name)) {
throw new Error('Duplicated registration');
}
if (!_.isFunction(builder)) {
throw new Error('Item builder has to be a function');
}
renderers[name] = builder;
}
export function getRenderer(type) {
if (!_.isFunction(renderers[type])) {
throw new Error('Unknown item type');
}
return renderers[type];
}

Просмотреть файл

@ -1,13 +0,0 @@
define([
'lib/backbone'
],
function(Backbone) {
'use strict';
// the base class of menu item
var MenuItem = Backbone.View.extend({
tagName: 'li',
});
return MenuItem;
});

Просмотреть файл

@ -1,118 +0,0 @@
define([
'lib/underscore',
'component/grid-toolbar/menuItem'
],
function(_, MenuItem) {
'use strict';
// the container for the menu buttons
/*
## props
* `Array` the list of radio menus.
## events
* `change:selection` the event will be triggered when the selection has changed.
* `click:item` the event will be triggered when this item is clicked
* `click:link` the event will be triggered when item's link text is clicked.
*/
var RadioGroupMenuItem = MenuItem.extend({
initialize: function(options) {
this.items = (options || {}).items || [];
this.isGroup = true;
this.checkedItem = null;
this._findCheckedItem();
_.each(this.items, this._initItem, this);
},
addItem: function(item) {
this.items.push(item);
this.$el.append(item.render().$el);
this._initItem(item);
},
removeItem: function(item) {
item.$el.remove();
var index = this.items.indexOf(item);
if (index !== -1) {
this.items.splice(index, 1);
}
// if delete the checked menu item, switch the checked state to other radio menu item.
if (item === this.checkedItem) {
this.checkedItem = this.items[0] || null;
this.checkedItem && this.checkedItem.check();
this.trigger('change:selection', this, this.checkedItem);
}
},
getItems: function() {
return this.items;
},
getItemCount: function() {
return this.items.length;
},
checkItem: function(item) {
if (item !== this.checkedItem) {
this.checkedItem && this.checkedItem.uncheck(); // Uncheck the original menu item
this.checkedItem = item;
item.check();
this.trigger('change:selection', this, item);
}
},
remove: function() {
_.each(this.items, function(item) {
item.$el.remove();
});
this.items = [];
},
_initItem: function(item) {
item.setGroup(this);
this._applyEvents(item);
},
_applyEvents: function(item) {
item.on('click', this._onClickItem, this);
item.on('click:link', function(item) {
this.trigger('click:link', this, item);
}, this);
},
_onClickItem: function(item) {
this.checkItem(item);
this.trigger('click:item', this, item);
},
_findCheckedItem: function() {
var items = this.items;
// Get the checked menu item index
var checkedItem = this.checkedItem = _.find(items, function(item) {
return item.checked;
}) || items[0];
if (!checkedItem) return ;
checkedItem.checked = true;
this.trigger('change:selection', this, checkedItem);
// Make other menu items's checked property false
_.each(items, function(item) {
item.checked = (item === checkedItem);
}, this);
},
render: function() {
var $ul = $('<ul/>');
_.each(this.items, function(item) {
$ul.append(item.render().$el);
});
this.$el.append($ul);
return this;
}
});
return RadioGroupMenuItem;
});

Просмотреть файл

@ -1,60 +0,0 @@
define([
'lib/underscore',
'lib/backbone',
'component/grid-toolbar/buttonMenuItem',
'component/grid-toolbar/template/radioMenuItem'
],
function(_, Backbone, ButtonMenuItem, menuItem) {
'use strict';
// the menu item of dropdown button
/*
The options can contain the following properties:
* text: the text of menu bar
* linkText: the text that show on the right of text
* checked: it's valid only if isRadio is true.
*/
var checkedClassName = 'glyphicon-ok';
var RadioMenuItem = ButtonMenuItem.extend({
initialize: function(options) {
this.options = _.defaults(options || {}, {
text : '',
checked : false,
linkText: null
});
this.checked = this.options.checked;
},
getText: function() {
return this.options.text;
},
setGroup: function(groupItem) {
this.groupItem = groupItem;
},
getGroup: function() {
return this.groupItem;
},
check: function(checked) {
this.checked = true;
this.$icon.addClass(checkedClassName);
},
uncheck: function() {
this.checked = false;
this.$icon.removeClass(checkedClassName);
},
render: function() {
this.options.checked = this.checked;
this.$el.html(menuItem(this.options));
this.$icon = this.$el.find('.glyphicon');
return this;
}
});
return RadioMenuItem;
});

Просмотреть файл

@ -1,40 +0,0 @@
define([
'lib/underscore',
'component/grid-toolbar/menuItem',
'component/grid-toolbar/template/subMenuItem'
], function(_, MenuItem, menuItem) {
/*
options can be:
* text: the text of menu bar
*/
var SubMenuItem = MenuItem.extend({
events: {
'mouseenter': '_onMouseOver',
'mouseleave': '_onMouseLeave'
},
_onMouseOver: function(event) {
this.$('.dropdown-submenu').removeClass('hidden');
},
_onMouseLeave: function(event) {
this.$('.dropdown-submenu').addClass('hidden');
},
render: function() {
var options = this.options || {};
this.$el.html(menuItem(options));
var submenu = this.$('.dropdown-submenu');
var children = options.children || [];
_.each(children, function(child) {
submenu.append(child.render().$el);
}, this);
return this;
}
});
return SubMenuItem;
});

129
js/toolbar-view.js Normal file
Просмотреть файл

@ -0,0 +1,129 @@
import _ from 'underscore';
import Backbone from 'backbone';
import { sequence } from './util.js';
import { getRenderer } from './item-register.js';
import './toolbar.less';
function defaultState() {
return {
items: [],
classes: [],
events: {},
};
}
function getItemContext(item) {
if (!_.isString(item.type)) {
throw new Error('Invalie item');
}
const id = item.id || _.uniqueId(`${item.type}-`);
const renderer = getRenderer(item.type);
return { id, item, renderer, children: [] };
}
function renderItemTree(root) {
const contexts = {};
const stack = [];
const renderItem = item => {
const context = getItemContext(item);
const { renderer, id } = context;
contexts[id] = context;
if (stack.length > 0) {
_.last(stack).children.push(id);
}
stack.push(context);
_.extend(context, renderer(_.defaults({ id }, item), renderItem));
stack.pop();
return context.html;
};
renderItem(root);
return contexts;
}
export class ToolbarView extends Backbone.View {
initialize({
id = _.uniqueId('toolbar-'),
classes = [],
items = [],
events = {},
}) {
this._root = { type: 'toolbar', id, classes, items };
this._events = events;
this._contexts = renderItemTree(this._root);
}
get id() {
return this._root.id;
}
events() {
const handlerHash = {};
const mergeEvents = events => {
_.each(events, (handler, key) => {
if (!_.has(handlerHash, key)) {
handlerHash[key] = [];
}
handlerHash[key].push(handler);
});
};
mergeEvents(this._events || {});
_.each(this._contexts || {}, context => mergeEvents(context.events));
return _.mapObject(handlerHash, sequence);
}
_removeContext(id) {
_.each(this._contexts[id].children, this._removeContext, this);
delete this._contexts[id];
}
get(id) {
return _.chain(this._contexts).result(id).result('item').value();
}
update(item) {
const id = item.id || this.id;
const itemNew = _.defaults({ id }, item, this.get(id));
if (id === this.id) {
if (!itemNew.type !== 'toolbar') {
throw new Error('The root item must be a toolbar');
}
this._root = itemNew;
}
if (_.has(this._contexts, id)) {
const contexts = renderItemTree(itemNew);
this._removeContext(id);
_.each(contexts, (context, id) => {
if (_.has(this._contexts, id)) {
throw new Error('duplicated item id');
}
this._contexts[id] = context;
});
if (this._isRendered) {
this.undelegateEvents();
this.$(`#${id}`).replaceWith(contexts[id].html);
this.delegateEvents();
}
} else {
console.warn(`Trying to update invalid item with id '${id}'`);
}
}
render() {
this._isRendered = true;
this.undelegateEvents();
this.$el.html(this._contexts[this.id].html);
this.delegateEvents();
return this;
}
}

4
js/toolbar.jade Normal file
Просмотреть файл

@ -0,0 +1,4 @@
.toolbar(class=classes, id=id)
each item in items
.toolbar-item
!= item.html

29
js/toolbar.js Normal file
Просмотреть файл

@ -0,0 +1,29 @@
import _ from 'underscore';
import { parseSelector } from './util.js';
import toolbarTemplate from './toolbar.jade';
function normalizeItem(item) {
if (_.isString(item)) {
return _.extend({ type: 'stub' }, parseSelector(item));
}
return item;
}
export function renderToolbar(toolbar, renderItem) {
const events = {};
const options = _.defaults({
items: _.map(toolbar.items, (item, index) => ({
html: renderItem(_.defaults({
tabindex: index === 0 ? 0 : -1,
}, normalizeItem(item)), events),
})),
}, toolbar, {
classes: [],
id: _.uniqueId('toolbar-'),
});
const html = toolbarTemplate(options);
return { html, events };
}

17
js/toolbar.less Normal file
Просмотреть файл

@ -0,0 +1,17 @@
.toolbar {
.toolbar-item {
display: inline-block;
}
.toolbar-item + .toolbar-item {
margin-left: 5px;
}
span.icon-left + span.text {
margin-left: 3px;
}
span.text + span.icon-right {
margin-left: 3px;
}
}

30
js/util.js Normal file
Просмотреть файл

@ -0,0 +1,30 @@
import _ from 'underscore';
export function parseSelector(selector) {
const classes = [];
const ids = [];
const regex = /([#\.])([^#\.]+)/g;
let match = null;
while ((match = regex.exec(selector)) !== null) {
if (match[1] === '#') {
ids.push(match[2]);
} else if (match[1] === '.') {
classes.push(match[2]);
}
}
const result = { classes };
if (!_.isEmpty(ids)) {
result.id = _.first(ids);
}
return result;
}
export function sequence(funcs) {
return function (...args) {
_.each(funcs, func => func.apply(this, args));
};
}

58
karma.conf.js Normal file
Просмотреть файл

@ -0,0 +1,58 @@
var _ = require('underscore');
var path = require('path');
function getWebpackConfig() {
var webpackConfig = _.omit(require('./webpack.config'), 'entry', 'externals');
_.defaults(webpackConfig, { module: {} });
webpackConfig.module.preLoaders = [{
test: /\.js$/,
include: path.resolve('./js/'),
loader: 'babel',
}, {
test: /\.js$/,
include: path.resolve('./js/'),
loader: 'isparta',
}].concat(webpackConfig.module.preLoaders || []);
return webpackConfig;
}
module.exports = function (config) {
config.set({
files: [
'speclist.js',
],
frameworks: [
'mocha',
],
client: {
mocha: {
reporter: 'html', // change Karma's debug.html to the mocha web reporter
},
},
reporters: ['mocha', 'coverage'],
preprocessors: {
'speclist.js': 'webpack',
},
webpack: getWebpackConfig(),
coverageReporter: {
dir: 'coverage/',
reporters: [
{ type: 'html', subdir: 'report-html' },
{ type: 'lcov', subdir: 'report-lcov' },
],
},
browsers: [
// 'PhantomJS',
'Firefox',
],
});
};

Просмотреть файл

@ -1,7 +1,85 @@
{
"testSuites": {
"unit": {
"cwd": "."
}
"main": "dist/backbone-toolbar.js",
"version": "0.0.1",
"files": [
"dist"
],
"scripts": {
"test": "gulp test coveralls",
"prepublish": "gulp prepublish"
},
"license": "MIT",
"name": "backbone-toolbar",
"description": "A Backbone based toolbar implementation.",
"keywords": [],
"author": {
"name": "Wei Wei",
"email": "lyweiwei@outlook.com"
},
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/backbone-toolbar.git"
},
"devDependencies": {
"babel-core": "^6.13.2",
"babel-loader": "^6.2.5",
"babel-preset-es2015": "^6.13.2",
"babel-preset-stage-3": "^6.11.0",
"backbone": "^1.3.3",
"bootstrap": "^3.3.7",
"bootstrap-webpack": "0.0.5",
"chai": "^3.5.0",
"css-loader": "^0.23.1",
"del": "^2.2.2",
"democase": "^0.1.9",
"eslint": "^3.3.1",
"eslint-config-xo": "^0.15.3",
"eslint-config-xo-space": "^0.14.0",
"exports-loader": "^0.6.3",
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "^0.9.0",
"gulp": "^3.9.1",
"gulp-coveralls": "^0.1.4",
"gulp-democase": "0.0.1",
"gulp-eslint": "^3.0.1",
"gulp-exclude-gitignore": "^1.0.0",
"gulp-file": "^0.3.0",
"gulp-jsdoc3": "^0.3.0",
"gulp-util": "^3.0.7",
"imports-loader": "^0.6.5",
"isparta-loader": "^2.0.0",
"istanbul-instrumenter-loader": "^0.2.0",
"jade": "^1.11.0",
"jade-loader": "^0.8.0",
"jquery": "^2.2.4",
"karma": "^1.2.0",
"karma-chrome-launcher": "^2.0.0",
"karma-coverage": "^1.1.1",
"karma-firefox-launcher": "^1.0.0",
"karma-mocha": "^1.1.1",
"karma-mocha-reporter": "^2.1.0",
"karma-phantomjs-launcher": "^1.0.1",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^1.8.0",
"less": "^2.7.1",
"less-loader": "^2.2.3",
"lodash": "^4.15.0",
"mocha": "^3.0.2",
"phantomjs-prebuilt": "^2.1.12",
"requirejs": "^2.2.0",
"source-map-loader": "^0.1.5",
"style-loader": "^0.13.1",
"underscore": "^1.8.3",
"url-loader": "^0.5.7",
"wdio-junit-reporter": "^0.1.0",
"wdio-mocha-framework": "^0.4.0",
"webdriverio": "^4.2.7",
"webpack": "^1.13.2",
"webpack-stream": "^3.2.0"
},
"peerDependencies": {
"backbone": "^1.3.3",
"jquery": "^2.2.4",
"underscore": "^1.8.3"
}
}
}

3
spec/.eslintrc.yaml Normal file
Просмотреть файл

@ -0,0 +1,3 @@
---
"rules":
"no-unused-expressions": 0

Просмотреть файл

@ -1,50 +0,0 @@
define([
'sinon',
'should',
'component/grid-toolbar/dropdown',
'component/grid-toolbar/buttonMenuItem'
],
function(sinon, should, Dropdown, ButtonMenuItem) {
'use strict';
describe('GridToolbar-dropdownbutton', function() {
var item1, item2, button;
// trigger the el's click event.
function click(btn) {
btn.$el.trigger('click');
}
// trigger the secondary element's click event.
function clickSecondary(btn) {
btn.$el.find('.secondary').trigger('click');
}
beforeEach(function() {
item1 = new ButtonMenuItem({title: "hello", linkText: "remove"});
item2 = new ButtonMenuItem({title: "world"});
button = new Dropdown({
title: "test",
menuItems: [item1, item2]
});
button.render();
});
it('onDidClickMenuItem is normal', function() {
var onClick = sinon.spy();
button.on('click:item', onClick);
click(item1);
onClick.should.have.been.called;
onClick.reset();
click(item2);
onClick.should.have.been.called;
});
it('onDidClickMenuItemSecondary', function() {
var onClick = sinon.spy();
button.on('click:link', onClick);
clickSecondary(item1);
onClick.should.have.been.called;
});
});
});

Просмотреть файл

@ -1,27 +0,0 @@
define([
'sinon',
'should',
'component/grid-toolbar/filterInput',
],
function(sinon, should, FilterInput) {
'use strict';
describe('GridToolbar-FilterInput', function() {
var filterInput;
function click(filterInput) {
filterInput.$el.find('button').trigger('click');
}
beforeEach(function() {
filterInput = new FilterInput({placeholder: "Hello World"});
filterInput.render();
});
it('onDidApply is OK', function() {
var onApply = sinon.spy();
filterInput.on('change', onApply);
click(filterInput);
onApply.should.have.been.called;
});
});
});

4
spec/index.js Normal file
Просмотреть файл

@ -0,0 +1,4 @@
var expect = require('chai').expect;
describe('backbone-toolbar', function () {
});

29
spec/util.js Normal file
Просмотреть файл

@ -0,0 +1,29 @@
import chai from 'chai';
import { parseSelector } from '../js/util.js';
const expect = chai.expect;
describe('parseSelector', function () {
it('should parse classes correctly', function () {
const selector = '.foo.foo-bar';
const classes = ['foo', 'foo-bar'];
expect(parseSelector(selector)).to.deep.equal({ classes });
});
it('should parse id correctly', function () {
const selector = '#foo#foo-bar';
const id = 'foo';
expect(parseSelector(selector)).to.deep.equal({ classes: [], id });
});
it('shoul parse mixed selector correctly', function () {
const selector = '.foo#bar.foo-bar';
const classes = ['foo', 'foo-bar'];
const id = 'bar';
expect(parseSelector(selector)).to.deep.equal({ classes, id });
});
});

2
speclist.js Normal file
Просмотреть файл

@ -0,0 +1,2 @@
var testsContext = require.context('./spec', true, /\.js$/);
testsContext.keys().forEach(testsContext);

Просмотреть файл

@ -1,75 +0,0 @@
.grid-toolbar .grid-toolbar-item-container {
margin-left: 10px;
display: inline-block;
}
.grid-toolbar div:first-child {
margin-left: 0px;
}
.grid-toolbar {
margin-bottom: 5px;
}
.grid-toolbar .grid-groupmenu-container {
display: inline-block;
}
.grid-toolbar .grid-filter-container {
min-width: 151px;
border: solid 1px #ccc;
display: inline-block;
vertical-align: middle;
}
.grid-toolbar .grid-filter-container:hover {
border-color: #606060;
}
.grid-toolbar .grid-filter-container .grid-filter-input {
width: 130px;
color: #333;
height: 22px;
padding-left: 4px;
border-width: 0px;
outline: none;
}
.grid-toolbar .dropdown-menu ul {
padding: 0px;
}
.grid-toolbar .dropdown-menu .anchor {
display: block;
padding: 7px 10px;
white-space: nowrap;
}
.grid-toolbar .dropdown-menu li .anchor:hover,
.grid-toolbar .dropdown-menu li .anchor:focus {
text-decoration: none;
color: #262626;
background-color: #e5e5e5;
}
.grid-toolbar .dropdown-menu .dropdown-submenu-item {
position: relative;
}
.grid-toolbar .dropdown-menu .dropdown-submenu {
position: absolute;
left: 100%;
top: 0px;
border: 2px solid #cccccc;
list-style: none;
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
background-color: #ffffff;
}
.grid-toolbar .dropdown-menu .dropdown-submenu-item .spritedimage {
top: 2px;
}
.filter-bar .panel-component .panel-body > *{
display: inline-block;
}

Просмотреть файл

@ -1,4 +0,0 @@
if leftIconClass
- classes = ['spritedimage', 'toolbar-icon', 'toolbar-icon-left', leftIconClass]
span(class=classes)
input.grid-menu(type="button" value=text || '')

Просмотреть файл

@ -1,12 +0,0 @@
define(['component/jade/util'], function(jade) { if(jade && jade['runtime'] !== undefined) { jade = jade.runtime; } return function template(locals) {
var buf = [];
var jade_mixins = {};
var jade_interp;
var locals_ = (locals || {}),leftIconClass = locals_.leftIconClass,classes = locals_.classes,text = locals_.text;
if ( leftIconClass)
{
classes = ['spritedimage', 'toolbar-icon', 'toolbar-icon-left', leftIconClass]
buf.push("<span" + (jade.cls([classes], [true])) + "></span>");
}
buf.push("<input type=\"button\"" + (jade.attr("value", text || '', true, false)) + " class=\"grid-menu\"/>");;return buf.join("");
}});

Просмотреть файл

@ -1,4 +0,0 @@
include ./menuItemTitle.jade
.anchor
+generateTitle(text, linkText)

Просмотреть файл

@ -1,20 +0,0 @@
define(['component/jade/util'], function(jade) { if(jade && jade['runtime'] !== undefined) { jade = jade.runtime; } return function template(locals) {
var buf = [];
var jade_mixins = {};
var jade_interp;
var locals_ = (locals || {}),text = locals_.text,linkText = locals_.linkText;
jade_mixins["generateTitle"] = function(text, linkText){
var block = (this && this.block), attributes = (this && this.attributes) || {};
if ( linkText)
{
buf.push("<span class=\"primary\">" + (jade.escape(null == (jade_interp = text) ? "" : jade_interp)) + "</span><a class=\"secondary text-right\">" + (jade.escape(null == (jade_interp = linkText) ? "" : jade_interp)) + "</a>");
}
else
{
buf.push(jade.escape(null == (jade_interp = text) ? "" : jade_interp));
}
};
buf.push("<div class=\"anchor\">");
jade_mixins["generateTitle"](text, linkText);
buf.push("</div>");;return buf.join("");
}});

Просмотреть файл

@ -1,10 +0,0 @@
div(type="button" data-toggle="dropdown" class="grid-menu-container grid-colchooser dropdown-toggle")
if leftIconClass != null
span(class="spritedimage toolbar-icon toolbar-icon-left " + leftIconClass)
span(class="grid-menu")
=title
if isShowRightIcon
span(class="spritedimage toolbar-icon " + (rightIconClass || "icon-arrowdown-normal"))
ul.dropdown-menu

Просмотреть файл

@ -1,17 +0,0 @@
define(['component/jade/util'], function(jade) { if(jade && jade['runtime'] !== undefined) { jade = jade.runtime; } return function template(locals) {
var buf = [];
var jade_mixins = {};
var jade_interp;
var locals_ = (locals || {}),leftIconClass = locals_.leftIconClass,title = locals_.title,isShowRightIcon = locals_.isShowRightIcon,rightIconClass = locals_.rightIconClass;
buf.push("<div type=\"button\" data-toggle=\"dropdown\" class=\"grid-menu-container grid-colchooser dropdown-toggle\">");
if ( leftIconClass != null)
{
buf.push("<span" + (jade.cls(["spritedimage toolbar-icon toolbar-icon-left " + leftIconClass], [true])) + "></span>");
}
buf.push("<span class=\"grid-menu\">" + (jade.escape(null == (jade_interp = title) ? "" : jade_interp)) + "</span>");
if ( isShowRightIcon)
{
buf.push("<span" + (jade.cls(["spritedimage toolbar-icon " + (rightIconClass || "icon-arrowdown-normal")], [true])) + "></span>");
}
buf.push("</div><ul class=\"dropdown-menu\"></ul>");;return buf.join("");
}});

Просмотреть файл

@ -1,14 +0,0 @@
.panel-component
.panel-body.filter-bar
.filter-bar-item.filter_title_collapsed=title
.filter-bar-item.filter_row_readonly
span.column=columnName
span.filter_operator=filterOperator
span.filterValue=filterValue
.filter-bar-item.filter_links
a.editFilterLink
.spritedimage.filtericonsalign.edit_icon(title=editText)
a.editFilterLink=editText
a.removeFilterLink
.spritedimage.filtericonsalign.remove_icon_gray_16(title=removeText)
a.removeFilterLink=removeText

Просмотреть файл

@ -1,7 +0,0 @@
define(['component/jade/util'], function(jade) { if(jade && jade['runtime'] !== undefined) { jade = jade.runtime; } return function template(locals) {
var buf = [];
var jade_mixins = {};
var jade_interp;
var locals_ = (locals || {}),title = locals_.title,columnName = locals_.columnName,filterOperator = locals_.filterOperator,filterValue = locals_.filterValue,editText = locals_.editText,removeText = locals_.removeText;
buf.push("<div class=\"panel-component\"><div class=\"panel-body filter-bar\"><div class=\"filter-bar-item filter_title_collapsed\">" + (jade.escape(null == (jade_interp = title) ? "" : jade_interp)) + "</div><div class=\"filter-bar-item filter_row_readonly\"><span class=\"column\">" + (jade.escape(null == (jade_interp = columnName) ? "" : jade_interp)) + "</span><span class=\"filter_operator\">" + (jade.escape(null == (jade_interp = filterOperator) ? "" : jade_interp)) + "</span><span class=\"filterValue\">" + (jade.escape(null == (jade_interp = filterValue) ? "" : jade_interp)) + "</span></div><div class=\"filter-bar-item filter_links\"><a class=\"editFilterLink\"><div" + (jade.attr("title", editText, true, false)) + " class=\"spritedimage filtericonsalign edit_icon\"></div></a><a class=\"editFilterLink\">" + (jade.escape(null == (jade_interp = editText ) ? "" : jade_interp)) + "</a><a class=\"removeFilterLink\"><div" + (jade.attr("title", removeText, true, false)) + " class=\"spritedimage filtericonsalign remove_icon_gray_16\"></div></a><a class=\"removeFilterLink\">" + (jade.escape(null == (jade_interp = removeText) ? "" : jade_interp)) + "</a></div></div></div>");;return buf.join("");
}});

Просмотреть файл

@ -1,2 +0,0 @@
input.grid-filter-input(placeholder=placeholder)
button.filter_search_icon_small

Просмотреть файл

@ -1,7 +0,0 @@
define(['component/jade/util'], function(jade) { if(jade && jade['runtime'] !== undefined) { jade = jade.runtime; } return function template(locals) {
var buf = [];
var jade_mixins = {};
var jade_interp;
var locals_ = (locals || {}),placeholder = locals_.placeholder;
buf.push("<input" + (jade.attr("placeholder", placeholder, true, false)) + " class=\"grid-filter-input\"/><button class=\"filter_search_icon_small\"></button>");;return buf.join("");
}});

Просмотреть файл

@ -1,8 +0,0 @@
mixin generateTitle(text, linkText)
if linkText
span.primary
=text
a.secondary.text-right
=linkText
else
=text

Просмотреть файл

@ -1,17 +0,0 @@
define(['component/jade/util'], function(jade) { if(jade && jade['runtime'] !== undefined) { jade = jade.runtime; } return function template(locals) {
var buf = [];
var jade_mixins = {};
var jade_interp;
;return buf.join("");
}});

Просмотреть файл

@ -1,8 +0,0 @@
include ./menuItemTitle.jade
.anchor
- var classNames = ['glyphicon', 'glyphicon-offset-left5']
- checked ? classNames.push("glyphicon-ok") : null
span(class=classNames)
+generateTitle(text, linkText)

Просмотреть файл

@ -1,23 +0,0 @@
define(['component/jade/util'], function(jade) { if(jade && jade['runtime'] !== undefined) { jade = jade.runtime; } return function template(locals) {
var buf = [];
var jade_mixins = {};
var jade_interp;
var locals_ = (locals || {}),checked = locals_.checked,text = locals_.text,linkText = locals_.linkText;
jade_mixins["generateTitle"] = function(text, linkText){
var block = (this && this.block), attributes = (this && this.attributes) || {};
if ( linkText)
{
buf.push("<span class=\"primary\">" + (jade.escape(null == (jade_interp = text) ? "" : jade_interp)) + "</span><a class=\"secondary text-right\">" + (jade.escape(null == (jade_interp = linkText) ? "" : jade_interp)) + "</a>");
}
else
{
buf.push(jade.escape(null == (jade_interp = text) ? "" : jade_interp));
}
};
buf.push("<div class=\"anchor\">");
var classNames = ['glyphicon', 'glyphicon-offset-left5']
checked ? classNames.push("glyphicon-ok") : null
buf.push("<span" + (jade.cls([classNames], [true])) + "></span>");
jade_mixins["generateTitle"](text, linkText);
buf.push("</div>");;return buf.join("");
}});

Просмотреть файл

@ -1,4 +0,0 @@
.anchor.dropdown-submenu-item
span=text || ''
span.spritedimage.grid-expand-icon.pull-right
ul.dropdown-submenu.hidden

Просмотреть файл

@ -1,7 +0,0 @@
define(['component/jade/util'], function(jade) { if(jade && jade['runtime'] !== undefined) { jade = jade.runtime; } return function template(locals) {
var buf = [];
var jade_mixins = {};
var jade_interp;
var locals_ = (locals || {}),text = locals_.text;
buf.push("<div class=\"anchor dropdown-submenu-item\"><span>" + (jade.escape(null == (jade_interp = text || '') ? "" : jade_interp)) + "</span><span class=\"spritedimage grid-expand-icon pull-right\"></span><ul class=\"dropdown-submenu hidden\"></ul></div>");;return buf.join("");
}});

13
wdio.conf.js Normal file
Просмотреть файл

@ -0,0 +1,13 @@
var path = require('path');
var democase = require('democase');
var demoSet = democase.loadSync(path.resolve(__dirname, 'demos'));
var config = demoSet.wdioConfig({
capabilities: [{
browserName: 'firefox',
}],
reporters: ['dot', 'junit'],
reporterOptions: { outputDir: './test-results/' },
});
module.exports = { config: config };

2
webpack.alias.js Normal file
Просмотреть файл

@ -0,0 +1,2 @@
// Config your webpack resolve.alias in this file
module.exports = {};

54
webpack.config.js Normal file
Просмотреть файл

@ -0,0 +1,54 @@
var _ = require('underscore');
var path = require('path');
var pkg = require('./package');
var webpackAlias = pkg.webpackAlias || {};
try {
webpackAlias = require('./webpack.alias');
} catch (e) { }
function getExternals() {
var deps = _.keys(pkg.peerDependencies);
var externals = _.object(deps, deps);
return _.reduce(_.pairs(webpackAlias), function (exts, pair) {
if (_.has(externals, pair[1])) {
exts[pair[0]] = pair[1];
}
return exts;
}, externals);
}
module.exports = {
entry: path.resolve('./js/index.js'),
output: {
path: path.join(__dirname, 'dist'),
filename: 'backbone-toolbar.js',
library: 'backbone-toolbar',
libraryTarget: 'umd',
umdNamedDefine: false,
devtoolModuleFilenameTemplate: function (info) {
if (path.isAbsolute(info.absoluteResourcePath)) {
return 'webpack-src:///backbone-toolbar-example/' + path.relative('.', info.absoluteResourcePath);
}
return info.absoluteResourcePath;
},
},
module: {
loaders: [
// jade
{ test: /\.jade$/, loader: 'jade-loader' },
// jade-end
// es2015
{ test: /\.js$/, exclude: /\bnode_modules\b/, loader: 'babel-loader' },
// es2015-end
// react
{ test: /\.less$/, loader: 'style!css!less' },
],
},
babel: { presets: ['es2015', 'stage-3'] },
externals: [getExternals()],
resolve: { alias: webpackAlias },
devtool: 'source-map',
};