├── .gitignore ├── conf └── eslint.json ├── spec ├── beat-blocks.spec.js ├── util │ ├── junk-drawer.spec.js │ └── config-manager.spec.js └── widget-base.spec.js ├── src ├── util │ ├── junk-drawer.js │ ├── handlebar-extensions.js │ └── config-manager.js ├── beat-blocks.js └── widget-base.js ├── bower.json ├── example ├── simple.js ├── link-example.js ├── compile-example.js ├── simple.html ├── compile-example.html └── link-example.html ├── package.json ├── Gruntfile.js ├── README.md └── dist ├── beat-blocks.min.js └── beat-blocks.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | bower_components -------------------------------------------------------------------------------- /conf/eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "browser": true 5 | }, 6 | "rules": { 7 | "no-underscore-dangle": 0, 8 | "no-unused-vars": 1, 9 | "no-shadow": 1 10 | } 11 | } -------------------------------------------------------------------------------- /spec/beat-blocks.spec.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var BeatBlocks = require('../src/beat-blocks'); 3 | 4 | describe("Basic Smoketests", function() { 5 | it("expects Beatblocks object to exist", function() { 6 | assert.ok(BeatBlocks); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /src/util/junk-drawer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * @file 5 | * 6 | * Utility functions/methods that don't fit anywhere else. 7 | */ 8 | 9 | 10 | var isNode = function() { 11 | return window === undefined; 12 | }; 13 | 14 | module.exports = { 15 | isNode: isNode 16 | }; 17 | -------------------------------------------------------------------------------- /spec/util/junk-drawer.spec.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var junkDrawer = require('../../src/util/junk-drawer'); 3 | 4 | describe("Junk Drawer", function() { 5 | describe("Smoke tests", function() { 6 | it("expects isNode to be a method", function() { 7 | assert.equal(typeof junkDrawer.isNode, "function"); 8 | }); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "beat-blocks", 3 | "version": "0.1.4", 4 | "authors": [ 5 | "Brandon Morrison " 6 | ], 7 | "license": "MIT", 8 | "ignore": [ 9 | "**/.*", 10 | "node_modules", 11 | "bower_components", 12 | "test", 13 | "tests" 14 | ], 15 | "main": "dist/beat-blocks.js", 16 | "devDependencies": { 17 | "grunt": "~0.4.5", 18 | "mocha": "~2.1.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /example/simple.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var WidgetBase = BeatBlocks.helpers.widgetBase; 4 | 5 | 6 | var ImageComponent = function(opts) { 7 | var config = { 8 | title: "", 9 | image: "", 10 | alt: "", 11 | caption: "" 12 | }; 13 | 14 | opts = (opts) ? opts : {}; 15 | 16 | config = _.defaults(opts, config); 17 | WidgetBase.call(this, config); 18 | }; 19 | 20 | ImageComponent.prototype = new WidgetBase(); 21 | 22 | BeatBlocks.addWidgetToRegistry("image", ImageComponent); 23 | -------------------------------------------------------------------------------- /src/util/handlebar-extensions.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Handlebars = require("handlebars"), 4 | moment = require("moment"); 5 | 6 | // format an ISO date using Moment.js 7 | // http://momentjs.com/ 8 | // moment syntax example: moment(Date("2011-07-18T15:50:52")).format("MMMM YYYY") 9 | // usage: {{#dateFormat creation_date format="MMMM YYYY"}} 10 | Handlebars.registerHelper("dateFormat", function(context, block) { 11 | if (global.moment) { 12 | var f = block.hash.format || "MMM Do, YYYY"; 13 | var myDate = new Date(context); 14 | return moment(myDate).format(f); 15 | } else { 16 | return context; // moment plugin not available. return data as is. 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /example/link-example.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var WidgetBase = BeatBlocks.helpers.widgetBase; 4 | 5 | var ImageComponent = function(opts) { 6 | var config = { 7 | title: "", 8 | image: "", 9 | alt: "", 10 | caption: "" 11 | }; 12 | 13 | opts = (opts) ? opts : {}; 14 | 15 | config = _.defaults(opts, config); 16 | WidgetBase.call(this, config); 17 | }; 18 | 19 | ImageComponent.prototype = new WidgetBase(); 20 | 21 | ImageComponent.prototype.link = function(elements) { 22 | var dateFormatter = d3.time.format('%c'); 23 | elements.select('.date').text('Today\'s date: ' + dateFormatter(new Date())); 24 | setInterval(function() { 25 | elements.select('.date').text('Today\'s date: ' + dateFormatter(new Date())); 26 | }, 1000); 27 | }; 28 | 29 | BeatBlocks.addWidgetToRegistry("image", ImageComponent); 30 | -------------------------------------------------------------------------------- /example/compile-example.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var WidgetBase = BeatBlocks.helpers.widgetBase; 4 | 5 | 6 | var ImageComponent = function(opts) { 7 | var config = { 8 | title: "", 9 | image: "", 10 | alt: "", 11 | caption: "" 12 | }; 13 | 14 | opts = (opts) ? opts : {}; 15 | 16 | config = _.defaults(opts, config); 17 | WidgetBase.call(this, config); 18 | }; 19 | 20 | ImageComponent.prototype = new WidgetBase(); 21 | 22 | 23 | 24 | ImageComponent.prototype.compile = function(elements, next) { 25 | var title = this.config('title'); 26 | this.config('title', title.toUpperCase()); 27 | console.log(this.config('title')); 28 | 29 | this.template(function(content) { 30 | elements 31 | .html(content); 32 | 33 | next(); 34 | }); 35 | }; 36 | 37 | BeatBlocks.addWidgetToRegistry("image", ImageComponent); 38 | -------------------------------------------------------------------------------- /src/beat-blocks.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * @file: Exposes various widgets and utilities to the global 5 | * context, mostly useful in a browser setting. 6 | */ 7 | 8 | // Utilities 9 | require("./util/handlebar-extensions"); 10 | 11 | // Widgets 12 | var widgetBase = require("./widget-base"); 13 | 14 | var widgetRegistry = require("./util/config-manager")(); 15 | 16 | module.exports = { 17 | widget: function(name, opts) { 18 | var Widget = widgetRegistry.config(name); 19 | if (Widget) { 20 | return new Widget(opts); 21 | } 22 | throw new Error("Can't find '" + name + "' widget."); 23 | }, 24 | addWidgetToRegistry: function(name, widget) { 25 | widgetRegistry.config(name, widget); 26 | }, 27 | listWidgets: function() { 28 | return widgetRegistry.list(); 29 | }, 30 | helpers: { 31 | widgetBase: widgetBase 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "beat-blocks", 3 | "repository": "https://github.com/phase2/beat-blocks.git", 4 | "version": "0.1.4", 5 | "devDependencies": { 6 | "browserify-shim": "^3.8.2", 7 | "grunt": "^0.4.5", 8 | "grunt-browserify": "^3.2.1", 9 | "grunt-complexity": "~0.3.0", 10 | "grunt-contrib-clean": "^0.6.0", 11 | "grunt-contrib-copy": "^0.8.0", 12 | "grunt-contrib-uglify": "^0.9.0", 13 | "grunt-contrib-watch": "^0.6.1", 14 | "grunt-eslint": "^12.0.0", 15 | "grunt-mocha-test": "^0.12.4", 16 | "load-grunt-tasks": "^3.1.0", 17 | "mocha": "^2.1.0", 18 | "time-grunt": "^1.0.0" 19 | }, 20 | "scripts": { 21 | "build": "grunt build --verbose", 22 | "test": "grunt test --verbose", 23 | "postinstall": "bower install --allow-root" 24 | }, 25 | "main": "src/beat-blocks.js", 26 | "dependencies": { 27 | "d3": "^3.5.3", 28 | "handlebars": "^2.0.0", 29 | "lodash": "^3.0.0", 30 | "moment": "^2.8.4" 31 | }, 32 | "browserify-shim": { 33 | "d3": "global:d3", 34 | "handlebars": "global:Handlebars", 35 | "lodash": "global:_", 36 | "moment": "global:moment" 37 | }, 38 | "browserify": { 39 | "transform": [ 40 | "browserify-shim" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /example/simple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Simple Component Example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 23 | 33 | 34 | -------------------------------------------------------------------------------- /example/compile-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Simple Component Example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 23 | 33 | 34 | -------------------------------------------------------------------------------- /example/link-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Simple Component Example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 24 | 34 | 35 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function (grunt) { 4 | // Load grunt tasks automatically. 5 | require("load-grunt-tasks")(grunt); 6 | 7 | // Time how long tasks take. Can help when optimizing build times 8 | require("time-grunt")(grunt); 9 | 10 | grunt.initConfig({ 11 | eslint: { 12 | options: { 13 | configFile: "conf/eslint.json" 14 | }, 15 | target: ["src/**/*.js", "src/*.js", "Gruntfile.js"] 16 | }, 17 | complexity: { 18 | generic: { 19 | src: ["src/*.js", "src/**/*.js"], 20 | options: { 21 | cyclomatic: 15, 22 | halstead: 25, 23 | maintainability: 80 24 | } 25 | } 26 | }, 27 | mochaTest: { 28 | dist: { 29 | src: ["spec/*.js", "spec/**/*.js"] 30 | } 31 | }, 32 | watch: { 33 | dist: { 34 | files: ["src/*.js", "src/**/*.js", "spec/**/*.js", "spec/*.js"], 35 | tasks: ["test", "build"] 36 | } 37 | }, 38 | browserify: { 39 | options: { 40 | browserifyOptions: { 41 | standalone: "BeatBlocks" 42 | } 43 | }, 44 | dist: { 45 | src: "./src/beat-blocks.js", 46 | dest: "./dist/beat-blocks.js" 47 | } 48 | }, 49 | uglify: { 50 | dist: { 51 | files: { 52 | "dist/beat-blocks.min.js": ["dist/beat-blocks.js"] 53 | } 54 | } 55 | } 56 | }); 57 | 58 | grunt.registerTask("test", ["complexity", "eslint", "mochaTest:dist"]); 59 | grunt.registerTask("build", ["browserify:dist", "uglify:dist"]); 60 | grunt.registerTask("default", ["test", "build", "watch"]); 61 | }; 62 | -------------------------------------------------------------------------------- /spec/widget-base.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require('assert'); 4 | var WidgetBase = require('../src/widget-base'); 5 | 6 | var widgetbase = new WidgetBase(); 7 | 8 | describe("WidgetBase", function() { 9 | describe("Smoke tests", function() { 10 | it("expects WidgetBase to exist", function() { 11 | assert.ok(widgetbase); 12 | }); 13 | it("expects WidgetBase.config to be a method", function() { 14 | assert.equal(typeof widgetbase.config, "function"); 15 | }); 16 | it("expects WidgetBase.template to be a method", function() { 17 | assert.equal(typeof widgetbase.template, "function"); 18 | }); 19 | it("expects WidgetBase.render to be a method", function() { 20 | assert.equal(typeof widgetbase.render, "function"); 21 | }); 22 | it("expects WidgetBase.compile to be a method", function() { 23 | assert.equal(typeof widgetbase.compile, "function"); 24 | }); 25 | it("expects WidgetBase.link to be a method", function() { 26 | assert.equal(typeof widgetbase.link, "function"); 27 | }); 28 | }); 29 | 30 | describe(".config()", function() { 31 | var widgetBase; 32 | 33 | beforeEach(function(done) { 34 | widgetBase = new WidgetBase(); 35 | done(); 36 | }); 37 | 38 | it("expects to be able to set config data", function() { 39 | var newConfig = {'foo': 'bar'}; 40 | assert.deepEqual(widgetBase.config(), {}); 41 | widgetBase.config(newConfig); 42 | assert.deepEqual(widgetBase.config(), newConfig); 43 | }); 44 | 45 | it("expects to be able to override config data", function() { 46 | var baseConfig = {'foo': 'foo', 'bar': 'bar', 'baz': 'baz'}; 47 | widgetBase.config(baseConfig); 48 | widgetBase.config({'foo': 'bar'}); 49 | 50 | var overriddenConfig = widgetBase.config(); 51 | assert.equal(overriddenConfig.foo, 'bar'); 52 | assert.equal(overriddenConfig.bar, 'bar'); 53 | assert.equal(overriddenConfig.baz, 'baz'); 54 | }); 55 | 56 | it("expects separate instances to have different configs", function() { 57 | var foo = new WidgetBase({'foo': 'foo'}); 58 | var bar = new WidgetBase({'bar': 'bar'}); 59 | assert.notDeepEqual(foo.config(), bar.config()); 60 | }); 61 | }); 62 | 63 | describe(".template()", function() { 64 | var widgetBase; 65 | 66 | beforeEach(function(done) { 67 | widgetBase = new WidgetBase(); 68 | done(); 69 | }); 70 | 71 | it ("expects template called without a template config item defined to throw an error.", function() { 72 | assert.throws(function() {widgetBase.template();}, /widget template/); 73 | }); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /spec/util/config-manager.spec.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Config = require('../../src/util/config-manager'); 3 | 4 | describe("Config Manager", function() { 5 | describe("Smoke tests", function() { 6 | var config; 7 | beforeEach(function() { 8 | config = Config(); 9 | }); 10 | it("expects config object to exist", function() { 11 | assert.ok(config); 12 | }); 13 | it("expects .config() method to exist", function() { 14 | assert.ok(config.config); 15 | }); 16 | it("expects .has() method to exist", function() { 17 | assert.ok(config.has); 18 | }); 19 | it("expects .list() method to exist", function() { 20 | assert.ok(config.list); 21 | }); 22 | }); 23 | 24 | describe(".config()", function() { 25 | var config; 26 | beforeEach(function() { 27 | config = Config(); 28 | }); 29 | 30 | it("Expects a call to the base function to call .config", function() { 31 | config({"foo": "bar"}); 32 | assert.deepEqual(config(), {"foo": "bar"}); 33 | }); 34 | 35 | it("Expects .config() to return an empty object if nothing has been set", function() { 36 | assert.deepEqual(config.config(), {}); 37 | }); 38 | 39 | it("Expects .config() to return current config object after something has been set", function() { 40 | config.config({foo: "bar"}); 41 | assert.deepEqual(config.config(), {foo: "bar"}); 42 | }); 43 | 44 | it("Expects .config('foo') to return the current config for 'foo'", function() { 45 | config.config({foo: "bar"}); 46 | assert.equal(config.config('foo'), "bar"); 47 | }); 48 | 49 | it("Expects .config('foo', 'bar') to set 'foo' to 'bar'", function() { 50 | config.config('foo', 'bar'); 51 | assert.equal(config.config('foo'), "bar"); 52 | }); 53 | 54 | it("expects separate instances to have different configs", function() { 55 | var foo = Config(); 56 | var bar = Config(); 57 | 58 | foo.config({"foo": "foo"}); 59 | bar.config({"bar": "bar"}); 60 | 61 | assert.notDeepEqual(foo.config(), bar.config()); 62 | }); 63 | 64 | it("expects config('foo.bar') to return a deep reference to foo.bar data", function() { 65 | var foo = Config(); 66 | foo.config({ 67 | "foo": { 68 | "bar": "baz" 69 | } 70 | }); 71 | 72 | assert.equal(foo.config("foo.bar"), "baz"); 73 | }); 74 | 75 | it("expects config('foo[0].bar') to return a deep reference to foo[0].bar data", function() { 76 | var foo = Config(); 77 | foo.config({ 78 | "foo": [ 79 | { 80 | "bar": "baz" 81 | } 82 | ] 83 | }); 84 | 85 | assert.equal(foo.config("foo[0].bar"), "baz"); 86 | }); 87 | }); 88 | describe(".has()", function() { 89 | var config; 90 | beforeEach(function() { 91 | config = Config(); 92 | }); 93 | 94 | it ("Expects .has() to return true if a value exists", function() { 95 | config.config({"foo": "foo"}); 96 | assert.ok(config.has("foo")); 97 | }); 98 | 99 | it ("Expects .has() to return false if a value does not exists", function() { 100 | assert.ok(!config.has("foo")); 101 | }); 102 | }); 103 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Beat Blocks 2 | 3 | A helpful scaffolding for building out configurable blocks that describe themselves. 4 | 5 | npm dependencies npm dependencies Bower version 6 | 7 | NPM 8 | 9 | ##Global Build Dependencies 10 | 11 | - [node/npm](http://nodejs.org/) - For package management 12 | - [grunt](http://gruntjs.com/) - For task management 13 | - [browserify](http://browserify.org/) - For bundling various src files into a single distribution 14 | 15 | ##Dependencies 16 | 17 | - [lodash](https://lodash.com/) 18 | - [d3](http://d3js.org/) 19 | - [Handlebars](http://handlebarsjs.com/) 20 | 21 | ##Usage 22 | 23 | 27 | 28 | See the examples folder of this repo for more detailed information. 29 | 30 | ##Dev tools install 31 | 32 | In root directory of the repo... 33 | 34 | npm install 35 | 36 | ##Development 37 | 38 | For development, be sure to have `grunt watch` running during development so that the bundled 39 | distribution is built. This also runs a code linter and unit tests on watch. 40 | 41 | Other helper grunt commands are 42 | 43 | grunt test - runs lint, complexity checks, and automated tests 44 | grunt build - compiles a distribution copy of the library 45 | 46 | ##Widget structure 47 | 48 | Widgets have a number of methods that make up a widget object. Understanding what each of these methods do is 49 | helpful in understanding how to create new and unique widgets. It's helpful to examine src/widget-base.js to 50 | get a better understanding of the specifics of how widgets behave. 51 | 52 | At their core, a widget is a template file and a simple javascript object. 53 | 54 | ###Helpful methods 55 | 56 | - .config() - Widget configuration get/setter 57 | Accepts 0, 1 or 2 parameters 58 | 59 | .config() - Returns the configuration of a widget as an object. 60 | .config(obj value) - Sets multiple config options, returns the full configuration of the widget as an object. 61 | .config(string key) - Returns the configuration of setting defined by the key of `string.` 62 | .config(string key, string|object value) - Sets the configuration for `key` to the value of `value`. 63 | 64 | - .render(element) - Renders a widget where at a particular DOM element. Element can either be a string (CSS selector) 65 | or a DOMElement. 66 | 67 | - .compile() - This method handles preparation of any configuration variables before rendering into a template. Override this 68 | method in your custom widgets if you want to load any external data into your template, or generate any additional 69 | content for your template before rendering. 70 | 71 | - .link() - This method handles any event reactions or DOM manipulation that needs to happen after the initial DOM render. 72 | Override this method in your custom widgets to define your own js behaviors. 73 | 74 | - .template() - The actual rendering method for a widget. By default, widgets use Handlebars.js for templating purposes. 75 | -------------------------------------------------------------------------------- /dist/beat-blocks.min.js: -------------------------------------------------------------------------------- 1 | !function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;"undefined"!=typeof window?b=window:"undefined"!=typeof global?b=global:"undefined"!=typeof self&&(b=self),b.BeatBlocks=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g