Module | Description |
---|---|
betajs-scoped | BetaJS-Scoped is a small module for scoped loading of modules and dependencies. |
betajs | BetaJS is a general-purpose JavaScript helper module. It contains a variety of helper functions and classes. |
betajs-browser | BetaJS-Browser is a client-side JavaScript framework for Browser-specific methods. |
betajs-data | BetaJS-Data is a general-purpose JavaScript framework for handling RESTful operations and ActiveRecord abstractions. |
betajs-server | BetaJS-Server is a server-side JavaScript framework extension for BetaJS. |
betajs-dynamics | BetaJS-Dynamics is a dynamic DOM templating engine. |
betajs-ui | BetaJS-UI is a library for enabling gestures and interactions such as drag and drop. |
betajs-flash | BetaJS-Flash is a Flash-JavaScript bridging framework |
betajs-media | BetaJS-Media is a JavaScript media framework |
betajs-media-components | BetaJS-Media-Components is a JavaScript media UI components framework |
betajs-debug | BetaJS-Debug is a library for debugging BetaJS-based applications. |
grunt-betajs-templates | Build BetaJS templates. |
grunt-betajs-docs-compile | Build BetaJS documentations based on JSDOC. |
betajs-codemirror | BetaJS-Codemirror is a Codemirror Plugin for the BetaJS Framework. |
betajs-richeditor | BetaJS-Richeditor is a rich editor plugin based on content editable using the BetaJS Framework. |
betajs-chartjs | BetaJS-ChartJS is a ChartJS Plugin for the BetaJS Framework. |
betajs-shims | This repository includes shims for ECMA Script that are not included in the official shims. |
betajs-workers | BetaJS-Workers is a light-weight library for accessing web workers uniformly and conveniently. |
mock-ajax-server | BetaJS Mock Ajax Server for Testing |
mock-file-server | BetaJS Mock File Server for Testing |
betajs-compile | BetaJS-Compile is a helper repository for building betajs modules. |
BetaJS-Scoped is a small module for scoped loading of modules and dependencies.
The Scoped system allows you to resolve and define module dependencies, similar to RequireJS.
The Scoped system needs to be loaded once in the beginning. It has no dependencies itself.
It can be either loaded directly by including Scoped:
<script src="//cdn.rawgit.com/betajs/betajs-scoped/master/dist/scoped.min.js"></script>
Or you can compile it into one of your libraries.
You can even include it more than once without any issues.
The Scoped system allows you to handle multiple libraries with separate namespaces and scopes.
Scopes in the Scoped system are organized in a tree structure.
Subscopes of a given scope inherit all namespacing definitions.
Given a scope, you can access the following different main pre-defined namespaces:
global
: this is the globally accessible namespace in JavaScript; in the browser, this would be window
root
: the root namespace of the scope in the scope treelocal
: the namespace local to the current scopedefault
: a namespace completely private to the current scopeparent
: the local namespace of the parent scopeNamespace declarations are always given in the following notation: parent:path
where parent
is an existing namespace declaration and path
is an object path identifier like Main.Sub.Subsub
.
A globally registered jQuery
would therefore be accessible via global:jQuery
.
You can also register new namespace for your own use:
Scoped.binding("my_namespace", "parent:path")
You can then use my_namepace
the same way you use the pre-defined namespaces.
In order to define a subscope, you'd write:
var MyScoped = Scoped.subScope();
A typical blueprint to structure your own library using Scoped is this:
(function () {
var Scoped = this.subScope();
Scoped.binding("module", "global:MyLibrary");
Scoped.binding("dependency1", "global:ExternalDependency1");
Scoped.binding("dependency2", "global:ExternalDependency2");
// Library code
}).call(Scoped);
This closure blueprint makes sure not to clutter the namespace of the rest of the environment.
One of the benefits of the Scoped system is that you can allow libraries to access external dependencies with different names and even overwrite the namespaces a library attaches to, so you can include two different versions of the same library without clashing their namespaces.
If you want a library to use or attach to different namespaces then its default ones, you need to overwrite them as follows before including the library:
Scoped.nextScope().binding("module", "global:MyLibraryAlternateNS", {
readonly: true
});
Defining dependencies is based on three primitives:
Scoped.require
: execute code once dependencies are resolvedScoped.define
: define a module in a namespace once dependencies are resolvedScoped.extend
: extend an existing module in a namespace once dependencies are resolvedThe syntax is as follows:
Scoped.require(['ns1:dependency1', 'ns2:dependency2', 'ns3:dependency3'], function (D1, D2, D3) {
// Execute once D1, D2, D3 are resolved.
});
Scoped.define('ns:module', ['ns1:dependency1', 'ns2:dependency2', 'ns3:dependency3'], function (D1, D2, D3) {
// Execute once D1, D2, D3 are resolved.
return {
// Return ns:module definition.
};
});
Scoped.extend('ns:module', ['ns1:dependency1', 'ns2:dependency2', 'ns3:dependency3'], function (D1, D2, D3) {
// Execute once D1, D2, D3 are resolved.
return {
// Return ns:module extension.
};
});
Debugging Scoped-based programs normally boils down to understanding why some dependencies are not being resolved.
The main reason for unresolved dependencies are typos.
The Scoped system has support for telling you which dependencies are not met at a certain point, so you can backtrack the root cause of the problem.
At any time, you can call the method Scoped.unresolved(namespace)
that will return an array of unresolved definitions within the given namespace.
For instance, if you want to know which definitions are unresolved in the globally accessible namespace, you would call Scoped.unresolved("global:")
.
You can use the Scoped system to pre-compile libraries based on Scoped in order to only include required sub modules.
The grunt configuration looks as follows:
grunt.initConfig({
scoped: {
dist: {
dest: "compiled.js",
externals: ["namespace1", "namespace2"],
src: [{
src: "source1.js",
bindings: {
"binding1": "namespace1",
"binding2": "namespace2"
},
full: false,
require: ["namespaceX", "namespaceY"]
}, {
...
}]
}
}
});
grunt.loadNpmTasks('betajs-scoped');
BetaJS is a general-purpose JavaScript helper module. It contains a variety of helper functions and classes.
BetaJS adds basic object orientation with static methods, object methods, attributes, class variables,
inheritance, mixins, helper classes and more to JavaScript.
Define a class uses the following syntax
ChildClass = ParentClass.extend(Namespace, InstanceExtension, ClassExtension, ClassStaticExtension);
ChildClass
is the newly created Class objectParentClass
is an existing Class object; in many cases, this is BetaJS.Class
Namespace
an optional namespace that the class should attach to; can be null
; see belowInstanceExtension
a json object defining methods and attributes; see belowClassExtension
a json object defining class methods and class variables; see belowClassStaticExtension
a json object defining class methods and class variables not shared with child classesHere is a basic example:
TestClass = BetaJS.Class.extend(null, {
y: 0,
fooBar: function (x) {
console.log("Test Class Instance", "fooBar", x, y);
}
}, {
staticFooBar: function (x) {
console.log("Test Class", "staticFooBar", x);
}
});
And here is how we would use it:
TestClass.staticFooBar(5);
var first = new TestClass();
first.y = 1;
first.foobar(2);
var second = new TestClass();
second.y = 3;
second.foobar(4);
Resulting in the following output:
Test Class staticFooBar 5
Test Class Instance fooBar 2 1
Test Class Instance fooBar 4 3
If we need inheritance by calling a parent method, we need the following extension syntax:
TestClass2 = TestClass.extend(null, function (inherited) {
return {
fooBar: function (x) {
console.log("TestClass2 Instance", "fooBar", x);
inherited.fooBar.call(this, x);
}
};
});
And here is how we would use it:
var third = new TestClass2();
third.y = 6;
third.foobar(7);
Resulting in the following output:
Test Class2 Instance fooBar 7
Test Class Instance fooBar 7 6
In many cases, you'd want to write code that get executed upon creation of the class, as well as code for the explicit deallocation of the object.
TestClass3 = TestClass2.extend(null, function (inherited) {
return {
constructor: function (y) {
console.log("Create Object");
inherited.constructor.call(this);
this.y = y;
},
destroy: function () {
console.log("Destroy Object");
inherited.destroy.call(this);
}
};
});
Here is how we would use it:
var fourth = new TestClass3(8);
fourth.foobar(9);
fourth.destroy();
Resulting in the following output:
Create Object
Test Class2 Instance fooBar 9
Test Class Instance fooBar 9 8
Destroy Object
Notifications allow you to send messages within an instance. This is particularly useful in combination with Mixins as it allows multiple methods to be executed when a certain event happens.
To trigger a notification, you call _notify
within the scope of an instance method:
this._notify("my_internal_event", param1, param2);
The same instance, derived instances as well as derived instances using mixins can now listen to it as follows:
_notifications: {
"my_internal_event": function (param1, param2) {
// handler code
}
}
You can also outsource the function:
_notifications: {
"my_internal_event": "internal_event_call_handler"
},
internal_event_call_handler: function (param1, param2) {
// handler code
}
A mixin is an additional set of methods and attributes that can be mixed in while extending a class:
SubClass = ParentClass.extend(null, [Mixin1, Mixin2, ..., {
// actual code of sub class
}]);
A mixin itself is a simple json object:
Mixin1 = {
method1: function () {...},
attr1: "foobar"
};
Instances of SubClass
contain all methods and attributes from all mixins:
var instance = new SubClass();
instance.method1();
var a = instance.attr1;
Mixins cannot inherit from a parent class, but they can react to instance notifications, e.g.:
Mixin2 = {
_notifications: {
"construct": function () {
// TODO
},
"destroy": function () {
// TODO
}
}
};
Here are some useful reflection methods that you can use at runtime.
Given an instance instance
, you can execute the following reflections:
instance.cls
: the class that instance
is an instance ofinstance.instance_of(cls)
: is this instance an instance of cls
?instance.destroyed()
: is this instance already destroyed?instance.weakDestroy()
: destroy only if not already destroyedinstance.cid()
: unique id of instanceinstance.auto_destroy(obj)
: automatically destroy obj
when destroying instance
Given a class cls
, you can execute the following reflections:
cls.parent
: the parent classcls.children
: all direct child classescls.classname
: class name as a stringcls.ancestor_of(cls2)
: is cls
an ancestor of cls2
? cls.is_class(cls2)
: is cls2
a class?cls.is_class_instance(instance)
: is instance
a class instance?cls.is_instance_of(instance)
: is instance
a class instance and an instance of this class?The event system allows you to emit different events with custom event data from class instances
that other components can listen to.
Event emitters can either inherit from the BetaJS.Events.Events class or include the BetaJS.Events.EventsMixin mixin.
var events = new BetaJS.Events.Events();
Events can be emitted by calling the trigger function. The first argument is the name of the event, all following arguments are custom data that is passed along.
events.trigger("event_name", event_data1, event_data2);
Other components can listen to events by using the on method:
events.on("event_name", function (event_arg1, event_arg2) {
// Do something
}, function_context);
The function context is optional.
Once the component is done listening for events, it can unregister by calling off:
events.off("event_name", null, function_context);
Instances of type Properties allow setting and getting of arbitrary attributes (including nested JSON).
var properties = new BetaJS.Properties.Properties({
a: "initial value"
});
properties.set("a", "second value");
var x = properties.get("a");
// x === "second value"
It implements the Events Mixin and emits change events whenever attributes are introduced, changed or removed.
properties.on("change:a", function (value, oldValue) {
console.log("a has been changed: ", value);
});
properties.set("a", "third value");
// will trigger the change event
It supports uni-directional and bi-directional data-binding to other properties instances.
Finally, it supports computed attributes that depend on other attributes in the object.
var properties_a = new BetaJS.Properties.Properties();
var x = properties_a.get("value_1");
// x === undefined
var properties_b = new BetaJS.Properties.Properties({
value_2: "This is Value 2"
});
var x = properties_b.get("value_2");
// x === "This is Value 2"
properties_a.set("value_1", "Some Property Value");
var x = properties_a.get("value_1");
// x === "Some Property Value"
var properties = new BetaJS.Properties.Properties();
properties.set("prop.value_a","This is value_a");
var x = properties.get("prop.value_a");
// x === "This is value_a"
Computed Properties are Properties that use other Properties as a Basis and the value
of the computed Property will be changed automatically if the Properties they are based on are changed.
This is done automatically by the Events System.
var properties = new BetaJS.Properties.Properties();
properties.set("value1","This is value 1");
properties.set("value2","This is value 2");
properties.compute("computed_value", function () {
return "The Values are: " + this.get("value1") + ", " + this.get("value2");
},["value1", "value2"]);
properties.get("computed_value");
//Will compute to: "The Values are: This is value 1, This is value 2"
properties.set("value1","Value 1 has changed");
properties.get("computed_value");
//Will now compute to: "The Values are: Value 1 has changed, This is value 2"
A collection is a dynamic list that allows for observers to listen for change events. Every item in a collection is based on a properties instance.
Create a Collection:
var collection = new BetaJS.Collections.Collection();
Create a Collection with Data inside:
var collection = new BetaJS.Collections.Collection({
objects: [
{title: "This is Item 1"},
{title: "This is Item 2"},
{title: "This is Item 3"}
]
});
You can add, remove and access items as follows:
collection.add(item);
collection.remove(item);
collection.getByIndex(0);
Item can either implement Properties or be a bare JSON object in which case it will be converted to a Properties instance.
It implements the Events Mixin and emits change events whenever items are added, changed or removed.
collection.on("add", function (item) {
// TODO: item has been added
});
collection.on("remove", function (item) {
// TODO: item has been removed
});
collection.on("update", function (item, key, value) {
// TODO: item has been updated
});
Given a collection, you might want to derive one (or many) subcollections that filter the content of the base collection.
var collection = new BetaJS.Collections.Collection();
You can then define the filtered collection as follows:
var filtered_collection = new BetaJS.Collections.FilteredCollection(collection, function (item) {
// return true if item should be contained in this collection.
}, optional_context);
The filtered collection updates automatically when the parent collection is updated, and vice versa.
var collection = new BetaJS.Collections.Collection(
{
item1: {
string: "This is Item 1",
value: 1
}
item2: {
string: "This is Item 2",
value: 2
}
item3: {
string: "This is Item 3",
value: 3
}
}
);
var filtered_collection = new BetaJS.Collections.FilteredCollection(collection, function (item) {
return item.get("value") === 2;
// The new collection will only contain item2 from the collection above.
// i.e. filtered_collection.count() == 1
}, optional_context);
// However if you now add an item to the original collection that has also a value of 2, it will also be in the filtered collection, i.e.:
collection.add({
string : "This is a fourth Item also with value 2",
value : 2
})
// i.e. filtered_collection.count() == 2
An iterator is an object that allows you to enumerate items of some list. The most commonly used iterator is based on an array:
var iterator = new BetaJS.Iterators.ArrayIterator(["a", "b", "c"]);
An iterator instance exposes two methods: hasNext, telling you whether there are more elements in the iterator
that you haven't seen yet, and next, which returns the next element in the iterator and moves the internal
position forward.
A typical iterator use looks as follows, enumerating all items:
while (iterator.hasNext()) {
var item = iterator.next();
// process the item
}
Iterators in general do not know how many elements are in the list. If you need to know the number of elements,
you need to iterate through the whole list.
The main advantages of iterators over using arrays or objects are as follows:
If you have an object instead of an array, you can create an object key iterator as follows:
var iterator = new BetaJS.Iterators.ObjectKeysIterator({a: 1, b: 2, c: 3});
As well as a value iterator:
var iterator = new BetaJS.Iterators.ObjectValuesIterator({a: 1, b: 2, c: 3});
In some cases, you want to perform certain transformations on a list before actually processing it. Such transformations
include filtering items, sorting the list, skipping items and limiting the number of items. You might even want to
combine some of these transformations.
Iterators can be chained in the following sense: given an iterator, you can automatically create a new iterator based on
it while performing a transformation on it.
For the sake of this paragraph, assume that we already have an iterator object called baseIterator (which could be an ArrayIterator).
For filtering items, you can use the FilteredIterator:
var iterator = new BetaJS.Iterators.FilteredIterator(baseIterator, function (item) {
// return true if item should be contained in iterator
}, this);
For sorting items, you can use the SortedIterator:
var iterator = new BetaJS.Iterators.SortedIterator(baseIterator, function (item1, item2) {
// return 1 if item1 > item2, -1 if item1 < item2 and 0 if item1 == item2
});
For skipping e.g. 10 items, you can use the SkipIterator:
var iterator = new BetaJS.Iterators.SkipIterator(baseIterator, 10);
For limiting the result to e.g. 50 items, you can use the LimitIterator:
var iterator = new BetaJS.Iterators.SkipIterator(baseIterator, 50);
You can also combine these iterators to, e.g. skip 10 items and return 50:
var iterator = new BetaJS.Iterators.LimitIterator(new BetaJS.Iterators.SkipIterator(baseIterator, 10), 50);
A promise
is an abstraction for handling asynchronous callbacks. It allows you to disconnect the handling of an asynchronous event from the emitter.
Normally, you'd write a function that requires an asynchronous callback as follows:
function emitter(callback) {
// some code that ends up calling callback()
}
You would consume it as follows:
emitter(function (...) {
// some code that should be called in the callback
});
Promises try to disconnect this as follows:
function emitter() {
var promise = BetaJS.Promise.create();
// some code that ends up calling promise.asyncSuccess();
return promise;
}
You would consume it as follows:
var promise = emitter();
// do some other stuff
promise.success(function (...) {
// some code that should be called in the callback
});
Now the handling of the event is disconnected from the emitting part.
Emitting promises is easy, and requires three steps:
var promise = BetaJS.Promise.create();
return promise;
promise.asyncSuccess(value)
and `promise.asyncError(error)
Once you receive a promise from an emitter, you can react to both success
and error
callbacks:
var promise = emitter();
promise.success(function (value) {
// code
});
promise.error(function (error) {
// code
});
The Promise
module has additional functionality for advanced use. We highlight some of it here.
Often, you want to transform the successful result value into something else but leave the error message alone:
var promise2 = promise1.mapSuccess(function (value) {
return do-computation-with-value;
});
This results in a new promise.
You can even combine two dependent promise calls this way:
var promise3 = promise1.mapSuccess(function (value) {
var promise2 = call_function_that_returns_a_promise(value);
return promise2;
});
The success call of promise3
then depends on the successful calls of promise1
and subsequently promise2
.
The Objs module includes support functions for handling javascript objects and arrays.
You can, for instance, iterate over arrays and objects as follows:
BetaJS.Objs.iter({key1: "value1", key2: "value2"}, function (value, key) {
console.log(key, " : ", value):
});
// Will print:
// key1 : value1
// key2 : value2
BetaJS.Objs.iter(["value1", "value2"], function (value, index) {
console.log(index, " : ", value):
});
// Will print:
// 0 : value1
// 1 : value2
You can also map all values in an array or object:
var result = BetaJS.Objs.map({key1: 4, key2: 5}, function (value, key) {
return value * value:
});
// result === {key1: 16, key2: 25}
var result = BetaJS.Objs.map([4, 5], function (value, index) {
return value;
});
// result === [16, 25]
The BetaJS system has a small library for generating unique IDs. This can be used for all kinds of purposes, but primarily for uniquely identifiying objects.
Since JavaScript has no notion of using pointers (to objects) as keys in hashes, it is impossible to save an identifier to an object without artificial ids.
Unique ids can be generated as follows:
var id = BetaJS.Ids.uniqueId(); // e.g. "123"
var idx = BetaJS.Ids.unqueId("prefix"); // e.g. "prefix123"
Given an object, we can read (if already set) or set and read its unique id as follows:
var obj = {...};
var id = BetaJS.Objs.objectId(obj);
Note that objectId
creates a new attribute within obj
called cid.
There is also a collection of different id generating classes that you can use for specific use cases:
IdGenerator
: Abstract Id Generator classPrefixedIdGenerator
: Takes a given generator and prefixes it with a stringRandomIdGenerator
: Generates ids randomly; no collision test.ConsecutiveIdGenerator
: Generates ids in a consecutive fashion using ints.TimedIdGenerator
: Uses the current time as ids in a unique fashion.Generators can be used as follows:
var generator = new TimedIdGenerator();
var id = generator.generate();
The system includes an abstract state machine that can be used for internal business logic as well as in combination with the router.
A state machine consists of a state machine host
instance which holds the current state
instance. States itself are defined by subclassing the abstract state class
.
To initialize a state machine, simply call:
var host = new BetaJS.States.Host();
There are different ways to define new states. An adhoc way, if the state machine is used as a singleton, is as follows:
host.register("A", {});
which registers a new state A
.
A more general way is by directly subclassing it:
var S = BetaJS.States.State.extend("BetaJS.Test.S");
var A = S.extend("BetaJS.Test.A");
var B = S.extend("BetaJS.Test.B");
After setting up the state machine, we start with an initial state:
host.initialize("A");
The current state can now be accessed via host.state()
.
To transition to the next state, we simply call host.state().next("B")
.
The Router package adds Routing functionality to BetaJS.
This functionality is particularly useful in Single Page Web applications, as it creates an easy abstraction for navigating the internal structure of the application.
The Router in the BetaJS system is completely abstract.
A router is created by creating an instance of the Router
object.
var router = new BetaJS.Router.Router();
The just created router
object does nothing at this point, because it does not
know about any routes. So the next step is binding routes to router
.
/**
* @param {string} name
* @param {string} routeUrl
*/
router.bind("test", "/test");
It is now possible to navigate to "test" by calling the navigate
method.
router.navigate("/test");
When the router navigates to "/test", it triggers an event
. Learn more about events by looking at the BetaJS.Events.Events
documentation. The name of the event is "dispatch:test". More generally, it is "dispatch:ROUTENAME".
Conducting an action based on the current route is as simple as listening for the proper event.
var events = BetaJS.Events.Events();
events.on("dispatch:test", function() {
console.log("Navigated to test.");
});
Now, suppose you want to navigate to the fourth post on a blog site. First, bind the route.
/**
* ...
* @param {function} callback
*/
router.bind("post", "/posts/(id:[0-9]*)", function(args) {
console.log("You navigated to blog post #" + args.id);
});
This route binding looks different because it contains a URL argument. It is possible to bind arguments from the url, which will be passed when the Event is omitted. Additionally, the previous example shows a new way to execute code during routing. The callback function will be called when the route is navigated to. It receives arguments from the url as the first argument to the callback.
Naturally, much more can be done with routers, but this is an introduction to the basic features.
You can combine routers with state machines. State machines in general provide a richer abstraction for state spaces while routers can be thought of a string serialization and parsing system for states.
Initialize the router, the state machine and the binder as follows:
var router = new BetaJS.Router.Router();
var host = new BetaJS.States.Host();
var binder = new BetaJS.Router.StateRouteBinder(router, host);
You can now register routes and state separately and connect some of them, or you bind them automatically:
binder.register("simple", "/simple");
binder.register("polymorphic", "/polymorphic/(key:first|second)");
This results in two routes, /simple
and /polymorphic/(key:first|second)
in the router, saved under the route names simple
and polymorphic
. Additionally states named Simple
and Polymorphic
are created within the state machine.
You can now perform navigation using the router:
router.navigate("/simple");
router.navigate("/polymorphic/second");
As well as state transitions using the state machine:
host.next("Simple");
host.next("Polymorphic", {key: "first"});
Since the router and state machine are linked to each other, transitions to either one are reflected in the other.
BetaJS-Browser is a client-side JavaScript framework for Browser-specific methods.
The Browser Router package adds routing functionality to the BetaJS Router that allows you to:
We assume that we already have created a router:
var router = new BetaJS.Router.Router();
Then, we can add the location hash route binder as follows:
var hashRouteBinder = new BetaJS.Browser.HashRouteBinder(router);
Similarly, we can add a location route binder:
var locationRouteBinder = new BetaJS.Browser.LocationRouteBinder(router);
Finally the history route binder allows you to control the browser history while navigating with your router:
var historyRouteBinder = new BetaJS.Browser.HistoryRouteBinder(router);
This module allows you to gather information about the browser environment, e.g.:
isIOS()
: iOS device?isAndroid()
: Android device?isEdge()
: Edge browser?isChrome()
: Chrome browser?isOpera()
: Opera browser?isInternetExplorer()
: IE browser?isFirefox()
: Firefox browser?isSafari()
: Safari browser?isMobile()
: mobile device?isDesktop()
: desktop device?iOSversion()
: iOS versionisWindows()
: Windows?isMacOS()
: Mac OS?isUnix()
: Unix?isLinux()
: Linux?internetExplorerVersion()
: IE versionThe loader allows you to asynchronously load scripts, styles and html:
BetaJS.Browser.Loader.loadScript("//.../script.js", function () {
// Loaded scripts.js
});
BetaJS.Browser.Loader.loadStyles("//.../styles.css", function () {
// Loaded styles.css
});
BetaJS.Browser.Loader.loadHtml("//.../web.html", function (content) {
// Loaded web.html
});
Different browsers require different techniques for asynchronously uploading of files and binary large objects (BLOBs).
This library takes care of that.
var uploader = BetaJS.Browser.Upload.FileUploader.create({
url: "//.../post",
source: file_object_or_blob,
serverSupportPostMessage: false // server supports postMessage fallback for old browsers?
});
uploader.on("success", function (data) {
// success data from server
}, this).on("error", function (data) {
// error data from server
}, this).on("progress", function (uploaded, total) {
// progress data
}, this);
uploader.upload();
There is also support for uploading multiple files at once:
var multiUploader = new BetaJS.Browser.Upload.MultiUploader();
multiUploader.addUploader(uploader1);
multiUploader.addUploader(uploader2);
multiUploader.addUploader(uploader3);
multiUploader.upload();
BetaJS-Data is a general-purpose JavaScript framework for handling RESTful operations and ActiveRecord abstractions.
The Query System allows you to formulate queries and evaluate them on a given instance.
We first introduce the query language itself:
/*
* atoms :== [atom, ...]
* atom :== string | int | bool | float
* queries :== [query, ...]
* query :== {pair, ...}
* pair :== key: value | $or : queries | $and: queries
* value :== atom | conditions
* conditions :== {condition, ...}
* condition :== $in: atoms | $gt: atom | $lt: atom | $gte: atom | $le: atom | $sw: atom | $ct: atom | all with ic
*/
A typical query for matching men older than 16 with their first names starting with an "S" would be:
{
"gender": "male",
"age": {
"$gt": 16
},
"first_name": {
"$sw": "S"
}
}
Given a query object query
and instance instance
, we can evaluate as follows:
BetaJS.Data.Queries.evaluate(query, instance)
Assuming the query from above, we would have:
evaluate(query, {"gender": "female", ...}) === false
evaluate(query, {"age": 16, ...}) === false
evaluate(query, {"first_name": "Guybrush", ...}) === false
evaluate(query, {"gender": "male", "age": 17, "first_name": "Simon"}) === true
While the query language itself only allows you to specify a condition that instances have to satisfy, constrained queries allow you to additionally limit the number of query results as well as specify the order in which the query results come in.
Given a query
, a constrainedQuery
looks as follows:
constrainedQuery = {
query: query,
options: {
limit: integer or null,
skip: integer or null,
sort: {
key1: 1 or -1,
key2: 1 or -1,
...
}
}
}
All options are optional.
Different query systems have different query capabilities. For instance, one system might not be able to skip search results while another system might not be able to sort. The query engine solves the following problem: given a constrained query, a query system with well-known query capabilities, compute the most efficient super query that the underlying query system can perform, perform the super query and map the super query to the actual query at hand.
It is used internally by the stores.
Stores define an abstract database interface. Stores derive from the abstract class BetaJS.Data.Stores.BaseStore
which defines the following asynchronous methods:
store.insert(instance).success(function (data) {
// Instance was inserted, and the updated data of instance is data (including the id)
}).error(function (error) {
// Could not insert instance
});
An instance is a json object. Every item in a store is given a unique id upon inserting it. It is then used to identify the item from thereon.
store.update(id, updatedData).success(function (data) {
// Instance was updated, and the updated data of instance is data
}).error(function (error) {
// Could not update instance
});
store.remove(id).success(function () {
// Instance was removed
}).error(function (error) {
// Could not removed instance
});
store.get(id).success(function (instance) {
// Instance was obtained
}).error(function (error) {
// Could not read instance
});
store.query(query, constraints).success(function (iterator) {
// Store was succesfully queried; the query result is an iterator over matched instances.
}).error(function (error) {
// Could not execute query
});
The most important simple stores are:
new BetaJS.Data.Stores.MemoryStore()
: stores all data in the temporary memory of the browser / NodeJSnew BetaJS.Data.Stores.LocalStore({}, localStorage)
: stores all data in the permanent memory of the browsernew BetaJS.Data.Stores.RemoteStore(...)
: stores all data in a store on a server across a network The RemoteStore implements a RESTful access to a server-side store. It can be instantiated as follows:
var store = new BetaJS.Stores.RemoteStore(ajax, restOptions, storeOptions);
Here, ajax
needs to be an instance of BetaJS.Net.AbstractAjax
, e.g. BetaJS.Browser.JQueryAjax
if operated within the context of a browser.
Second, restOptions
is a JSON object accepting the following optional configuration parameters:
Delegator Stores are Store classes that delegate calls to other, underlying stores while transforming the requests in different useful ways:
Store indices are database indices. By adding indices for certain keys to your stores, you can speed up queries. Indices can be created as follows, given a store
:
store.indices.first_name = new BetaJS.Data.Stores.MemoryIndex(store, "first_name");
store.indices.last_name = new BetaJS.Data.Stores.MemoryIndex(store, "last_name");
The modelling system puts an abstraction layer on top of stores that allows us to treat database instances as properties such that changes in the properties instance are reflected back to the database and vice versa.
The role of json instances in the store system is provided by so-called Models
whereas the role of the stores is provied by so-called Tables
.
A Model
class is a sub class of Properties
. When you define your own model, you always define a scheme:
var MyModel = BetaJS.Data.Modelling.Model.extend(null, {
}, function (inherited) {
return {
_initializeScheme: function () {
var scheme = inherited._initializeScheme.call(this);
scheme.first_name = {
type: "string"
};
scheme.last_name = {
type: "string"
};
return scheme;
}
};
});
You usually do not instantiate model instances directly. Instead, you create a Table
instance based on a store
(which stores the actual data):
var myTable = new BetaJS.Data.Modelling.Table(store, MyModel);
This table instance can now be used to create a new model:
var myModel = myTable.newModel({
first_name: "Donald",
last_name: "Duck"
});
myModel.save().success(function () {...}).error(function () {...});
Model instances can be removed from the table:
myModel.remove();
Model instances are automatically updated and saved when you change their property attributes:
myModel.set("first_name", "Daisy");
Model instances can be obtained from the table as follows:
myTable.findById(id).success(function (myModel) {
...
}).error(function (error) {...});
Model instances can be obtained from the table as follows:
myTable.findBy({first_name: "Donald"}).success(function (myModel) {
...
}).error(function (error) {...});
Model instances can be obtained from the table as follows:
myTable.allBy(query, constraints).success(function (modelIterator) {
...
}).error(function (error) {...});
Validators allow you to add value validations to your model scheme that prevent invalid attributes from being saved to the store. You can add validators to your model scheme as follows:
_initializeScheme: function () {
var scheme = inherited._initializeScheme.call(this);
scheme.first_name = {
type: "string",
validate: new BetaJS.Data.Modelling.Validators.PresentValidator()
};
scheme.last_name = {
type: "string",
validate: new BetaJS.Data.Modelling.Validators.PresentValidator()
};
return scheme;
}
The validate arguments admits both direct validator instances as well as an array of validator instances if you need to attach more than one validator.
The system currently supports the following validators:
Query Collections are collections which are based on a store or a table query. Its items are either instantiated properties from the query result (if it is based on a store) or instantied models (if it is based on a table).
Query Collections should be seen as a read only representation of a query. You should never add elements directly to a query collection. Instead, you should add the instance to the store or the table.
var tableQC = new BetaJS.Data.Collections.TableQueryCollection(table, query, options);
var storeQC = new BetaJS.Data.Collections.StoreQueryCollection(store, query, options);
The following (among other) options are supported:
active
: should the query collection actively update itself? (default false)incremental
: whenever the query is updated, should only the diff be queried? (default true)active_bounds
: if the query collection is bounded and new items are added, should the bound be extended? (default true)range
: limit query results (default disabled)auto
: should the system immediately run the query (default false)BetaJS-Dynamics is a dynamic DOM templating engine.
BetaJS Dynamics is a JavaScript frontend framework. It generally has two parts:
The Javascript Controller:
var dynamic = new BetaJS.Dynamics.Dynamic({
element: document.querySelector("some_element").innerHTML,
initial : {
attrs : {
some_attribute : "This is some Text",
some_boolean : true
}
}
});
The HTML Element:
<script type='text/template'>
<some_element ba-if="{{some_boolean}}">{{some_attribute}}</some_element>
</script>
Will evaluate to
<some_element>This is some Text</some_element>
Our "Hello World" a one-page html that loads the BetaJS framework and some other required files in the head. And runs a very basic "Hello World" script in the body.
If everything is set up correctly you should see "Hello World" if you load the helloworld.html displayed in the box below in the your browser, and making sure that all the assets are present as well.
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>Hello World</title>
<script src="beta.js"></script>
<script src="betajs-browser-noscoped.js"></script>
<script src="betajs-dynamics.js"></script>
</head>
<body>
<div id="helloworld">{{replaced_value}}</div>
<script>
var dynamic = new BetaJS.Dynamics.Dynamic({
element: document.querySelector("#helloworld")
});
dynamic.set("replaced_value", "Hello World");
dynamic.activate();
</script>
</body>
</html>
<div id="helloworld"> {{replaced_value}} </div>
Inside {{}} ist an attribute property that we can control from other parts of the application.
dynamic = new BetaJS.Dynamics.Dynamic({
element: document.querySelector("#helloworld")
});
Here, we are creating a new dynamic, and make it responsible for handling the subtree under the dom node #helloworld
.
dynamic.set("replaced_value", "Hello World");
Here, we create set an attribute "replaced_value" and give it the value "Hello World". When the dynamic is loaded, the handlebars {{replaced_value}} will be replaced by its value in the DOM so that we see "Hello World" on the actual site.
Dynamic attributes are a key value store based on the BetaJS Properties class.
The HTML view will update itself automatically when attributes in the dynamic are changed.
dynamic.activate();
This last part activates the dynamic and binds the HTML to the JS controller.
Dynamics are at the very core of this framework, representing its view components.
Handlebars are a way to do one of two things:
<div class="{{some_class}}">Hello</div>
Partials are a form of pseudocode that allow you to attach particular behaviour to nodes in the DOM/view.
<div ba-if="">Hello</div>
These blueprints iareintented as a quick reference for how to integrate the most commonly used dynamic functionalities.
BetaJS.Dynamics.Dynamic.extend("BetaJS.Dynamics.Dynamictemplate", {
//Only use one of the following three concurrently
templateUrl : "templates/template.html",
template : "<div>Internal Template {{some_attribute}}</div>",
element : document.querySelector(".someclass"),
bindings : {
parent_dynamic_attribute: "<:attribute_in_parent_dynamic"
},
scope : {
parent_dynamic: "<"
},
attrs : {
some_attribute: true,
computed_attribute : 'default value',
dependency1 : 'Hel',
dependency2 : 'lo'
},
collections : {
some_collection: ['one','two']
},
computed : {
'computed_attribute:dependency1,dependency2' : function () {
return dependency1 + dependency2;
}
}
create : {
//Reading and writing attribute values
var attribute = this.get('some_attribute');
this.set('some_attribute',false);
//Manipulating Collections
this.get('some_collection').add('three');
this.get('some_collection').remove('one');
//Accessing other Dynamics
var scope_attr = this.scope.parent_dynamic.get('attribute_in_parent_dynamic');
var bind_attr = this.get('parent_dynamic_attribute');
if (scope_attr == bind_attr)
console.log('This is set up correctly');
//Accessing functions
this.call('some_function');
},
functions : {
some_function : () {
this.element() //to access the element itself
}
},
_afterActivate : function (element) {
console.log('This message is displayed after the dynamic is activated');
//element passes you the element where the Dynamic is active on as a DOM element
}
});
<somedynamic>
<-- Basics -->
<-- Data bindings -->
<databinding>{{attribute}}</databinding>
<-- This is a unidirectional binding, meaning the DOM Element will get updated -->
<-- if the Attribute in the Dynamic changes, but it will not update the Attribute in the Dynamics if the DOM changes -->
<databinding>{{=attribute}}</databinding>
<-- This is a bidirectional binding -->
<-- Click Events -->
<button ba-click='some_function()'>Click me</button>
<button ba-click='attribute = false'>Click me</button>
<button ba-click='attribute = !attribute'>Click me</button>
<button ba-tap='some_function()'>Click me</button> <-- Tap is for use in mobile -->
<-- If/Show -->
<div ba-if='{{true}}'>This DOM Element will always be loaded</div>
<div ba-if='{{false}}'>This DOM Element will never be loaded</div>
<div ba-if='{{attribute}}'>This DOM Element will be loaded if {{attribute}} evluates to 'true'</div>
<div ba-if='{{attribute.subattribute}}'>This DOM Element will be loaded if {{attribute.subattribute}} evluates to 'true'</div>
<div ba-if='{{attribute.get('property')}}'>This DOM Element will be loaded if {{attribute.get('property')}} evluates to 'true'</div>
<div ba-if='{{attribute == 'load'}}'>This DOM Element will be loaded if {{attribute}} evluates to 'load'</div>
<div ba-show='{{attribute}}'>This DOM Element will be displayed if {{attribute}} evluates to 'true'</div>
<-- Classes -->
<div ba-class='{{{
'always' : true,
'sometimes' : attribute,
'othertimes' : attribute.get('property') == 'other',
'never' : false
}}}'>Some random text</div>
<-- Repeat -->
<div ba-repeat="{{item :: ['1','2','3']}}">
<div>
<div>The html inside the ba-repeat item will be repeated</div>
<div>The item tag will contain the value of the array (1/2/3): {{item}}</div>
</div>
</div>
<-- Advanced Handlebar Examples -->
<div class="{{attribute}}">Some random text</div>
<div onmouseover="{{alert('You moved the mouse over this Element')}}"></div>
<ba-{{attribute}}>Some random text</ba-{{/attribute}}>
<yourcustomtag-{{attribute}}>Some random text</yourcustomtag-{{/attribute}}>
//Note
<{{attribute}}>This won't work, because of the way the Browser renders elements</{{/attribute}}>
</somedynamic>
Following is a more detailed description of the Javascript Contoller part of the Dynamics System.
For a quick Overview please Reference the Blueprint JS:link
You can create Dynamics both as instances and as classes which can be instantiated.
var dynamic = new BetaJS.Dynamics.Dynamic({
templateUrl : "templates/template.html",
element: document.body
});
//This is only necessary if this dynamic is stand-alone
//If the dynamic is the child of another dynamic it is not necessary
dynamic.activate();
You can create new dynamics classes using the code below:
BetaJS.Dynamics.Dynamic.extend("BetaJS.Dynamics.Classname", {
templateUrl: "templates/classname.html",
}).register("ba-htmltagname");
These classes can be instanciated in two ways,
either in the Javascript,
var new_instance = new BetaJS.Dynamics.Classname();
new_instance.activate();
or by just adding the DOM tagname :
<ba-htmltagname></ba-htmltagname>
These code snippets can be quickly copy&pasted:
BetaJS.Dynamics.Dynamic.extend("BetaJS.Dynamics.Classname", {
templateUrl: "templates/%.html",
//The code above will evaluate equivalent to the code below
templateUrl: "templates/classname.html",
//You can do multiples of this: templates/%/%.html will evaluate to
//templates/classname/classname.html
}).register("ba-htmltagname");
BetaJS.Dynamics.Dynamic.extend("BetaJS.Dynamics.Classname", {
templateUrl: "templates/classname.html",
}).register();
//The code above will evaluate equivalent to the code below
}).register("ba-classname");
You can combine this as in the example below:
BetaJS.Dynamics.Dynamic.extend("BetaJS.Dynamics.Classname", {
templateUrl: "templates/%/%.html",
}).register();
There are currently two ways to link dynamic code to a template:
They are mutually exclusive.
dynamic = new BetaJS.Dynamics.Dynamic({
templateUrl : "templates/template.html", //This contains the relative file path to an external template
template : "<div>Internal Template</div>"
});
Instead of using templates, you can also link a dynamic to an already existing html element which will then be evaluated by the dynamics system.
dynamic = new BetaJS.Dynamics.Dynamic({
element : document.querySelector(".someclass")
});
The Code here gets exectued after the dynamic has finished loading in the DOM.
Scopes offer a way to directly access Attributes, Collections and Methods in other Dynamics.
You access other Dynamics by either directly addressing Parent or Children of certain dynamics.
Basic Usage Example:
BetaJS.Dynamics.Dynamic.extend("BetaJS.Dynamics.Childdynamic", {
template : "<div>{{text}}</div>"
attrs : {text: "Hello World!"}
functions : {
call_me : function () {
alert("You wanted an alert?");
}
}
});
BetaJS.Dynamics.Dynamic.extend("BetaJS.Dynamics.ScopeExample", {
template : "<ba-childdynamic><ba-childdynamics>"
scopes : {
child_dynamic: ">[tagname='ba-childdynamic']",
},
create : {
//Examples of Accessing another Dynamics
var child_text = this.scopes.child_dynamic.get('text');
// child_text will evaluate to "Hello World"
this.scopes.child_dynamic.set('text','New Hello World Text');
//The child dynamic evaluation will now be changed from
//"<div>Hello World</div> to
//"<div>New Hello World Text</div> to
this.scopes.child_dynamic.call('call_me');
// This will call the alert in the childdynamic (You wanted an alert?)
}
});
More examples:
BetaJS.Dynamics.Dynamic.extend("BetaJS.Dynamics.ScopeExample", {
scopes : {
//This gives you the Dynamic
parent_dynamic: "<",
specific_child: ">[tagname='ba-tagname']",
specific_in_all_children: ">+[tagname='ba-tagname']"
//This gives you a query result of the Dynamics
child_dynamics: ">",
all_children: ">+",
parents_children_dynamics: "<>",
parents_childrens_children_dynamics: "<>>",
},
create : {
//Examples of Accessing other Dynamics
//Directly
var scope_attr = this.scopes.parent_dynamic.get('attribute_in_parent_dynamic');
// scope_attr is an attribute from the parent_dynamic
//Query Collection
var json_data = this.scopes.child_dynamics.get(0);
// json_data is a JSON object that contains the attribute values from the first child_dynamic
var json_data = this.scopes.child_dynamics.get(0).materialize();
}
});
Events Channels are a way to encapsulate message sending between dynamics. By default, there is a global channel that all dynamics can subscribe to:
BetaJS.Dynamics.Dynamic.extend(null, {
channels: {
"global:test": function () {
// Execute when the event test has been triggered on the global channel
}
}
}).register("ba-receiver");
BetaJS.Dynamics.Dynamic.extend(null, {
functions: {
foobar: function () {
// Trigger event test on the global channel
this.channel("global").trigger("test");
}
}
}).register("ba-sender");
In some case, you might want to restrict a channel to one dynamic and all its subdynamics. To do so, you register your own channel in the parent dynamic and subscribe to it in the subdynamics:
BetaJS.Dynamics.Dynamic.extend(null, {
registerchannels: ["mychannel"]
}).register("ba-parent");
BetaJS.Dynamics.Dynamic.extend(null, {
channels: {
"mychannel:test": function () {
// Execute when the event test has been triggered within the parent dynamic mychannel
}
}
}).register("ba-childreceiver");
BetaJS.Dynamics.Dynamic.extend(null, {
functions: {
foobar: function () {
// Trigger event test within the parent dynamic mychannel
this.channel("mychannel").trigger("test");
}
}
}).register("ba-childsender");
Partials are placed onto HTML DOM elements/nodes and are evaluated by the dynamics system, manipulating the related DOM elements/nodes and thus changing the view.
<div ba-partialname="related attributes/code"><div>
The ba-if Partial is used to automatically add or remove DOM elements and subdynamics depending on the boolean value of a given condition.
<div ba-if="{{1 === 1}}"><h1>Hi</h1><div>
Will evaluate to
<div><h1>Hi</h1><div>
And
<div ba-if="{{1 === 2}}"><h1>Hi</h1><div>
Will evaluate to
<div><div>
The ba-click partial executes the code passed into it when the user clicks on the related DOM element. (or calls a function defined in the dynamic)
Will call an alert pop-up
<div ba-click="alert('Hi')"><h1>Hi</h1><div>
Will Show/Hide the h1-element when you click on the surrounding div element.
<div class="some_class" ba-click="boolean != boolean">
<h1 ba-show="boolean">Hi</h1>
<div>
some_dynamic = new BetaJS.Dynamics.Dynamic({
element: document.querySelector(".some_class"),
attrs : {
boolean : true
}
});
Will also call an alert pop-up
<div class="some_class" ba-click="some_function()">
<h1>Hi</h1>
<div>
some_dynamic = new BetaJS.Dynamics.Dynamic({
element: document.querySelector(".some_class"),
functions : {
some_function : function () {
alert('Another alert');
}
}
});
The ba-tap partial is similar to the ba-click and is meant to be used with touchscreen interfaces for faster click response times.
Will call an alert pop-up
<div ba-tap="alert('Hi')"><h1>Hi</h1><div>
Will toggle the heading element when you click on the surrounding div element.
<div class="some_class" ba-tap="boolean != boolean">
<h1 ba-show="boolean">Hi</h1>
<div>
some_dynamic = new BetaJS.Dynamics.Dynamic({
element: document.querySelector(".some_class"),
attrs : {
boolean : true
}
});
Will call an alert pop-up
<div class="some_class" ba-tap="some_function()">
<h1>Hi</h1>
<div>
some_dynamic = new BetaJS.Dynamics.Dynamic({
element: document.querySelector(".some_class"),
functions : {
some_function : function () {
alert('Another alert');
}
}
});
The ba-class partial dynamically sets the css class of a given element based on the boolean value of a given expression
<div class="some_class" ba-class="{{{
"class_a" : true,
"class_b" : 1===2,
"class_c" : some_property
}}}">
<h1>Hi</h1>
<div>
some_dynamic = new BetaJS.Dynamics.Dynamic({
element: document.querySelector(".some_class"),
attrs : {
some_property : true
}
});
Evaluates to
<div class="some_class class_a class_c"
<h1>Hi</h1>
<div>
The ba-show partial is used to automatically show or hide DOM Elements depending on the boolean value of a given condition.
ba-show is really just a convenience method to show/hide DOM elements.
If ba-show evaluates to false it will apply the css property "display: none" to the DOM element it is placed on. In comparison, if ba-if evaluates to false it will also free all the resources of all underlying dynamics.
ba-if:
ba-show:
<div ba-show="{{1 === 1}}"><h1>Hi</h1><div>
Will evaluate to
<div><h1>Hi</h1><div>
And
<div ba-show="{{1 === 2}}"><h1>Hi</h1><div>
Will evaluate to
<div><h1 style='display: none'></h1><div>
<div class="some_class" ba-show="{{some_attribute}}">
<h1>Hi</h1>
<div>
some_dynamic = new BetaJS.Dynamics.Dynamic({
element: document.querySelector(".some_class"),
attrs : {
some_attribute: true
}
});
Will evaluate to
<div class="some_class">
<h1>Hi</h1>
<div>
Now if we call
some_dynamic.set("some_attribute",false);
The View will be changed to
<div class="some_class">
<h1 style='display: none'>Hi</h1>
<div>
The ba-if partial is used to automatically display or hide DOM Elements depending on the boolean value of a given condition.
If ba-if evaluates to false it will free all the resources of all underlying dynamics. In comparison ba-show does not do this.
ba-if:
ba-show:
<div ba-if="{{1 === 1}}"><h1>Hi</h1><div>
Will evaluate to
<div><h1 style="display:block;">Hi</h1><div>
And
<div ba-if="{{1 === 2}}"><h1>Hi</h1><div>
Will evaluate to
<div><h1 style="display:none;">Hi</h1><div>
<div class="some_class" ba-if="{{some_attribute}}">
<h1>Hi</h1>
<div>
some_dynamic = new BetaJS.Dynamics.Dynamic({
element: document.querySelector(".some_class"),
attrs : {
some_attribute: true
}
});
Will evaluate to
<div class="some_class" style="display:block;">
<h1>Hi</h1>
<div>
Now if we call
some_dynamic.set("some_attribute",false);
The View will be changed to
<div class="some_class">
<div>
The ba-ignore partial disables the loading of the dynamic it is placed on.
This is particulary useful if you need to bind an outside view engine to a particular dom element.
The ba-repeat partial repeats the containing DOM elements of the DOM Elements it is placed in a certain number of times.
<ul ba-repeat="{{ i :: [1,2] }}">
<li>{{i}}</li>
</ul>
Evaluates to
<ul>
<li>1</li>
<li>2</li>
</ul>
dynamic = new BetaJS.Dynamics.Dynamic({
element: document.querySelector(".some_class"),
collections : {
named_collection: [
{item_name : "Apple", item_index : "1"},
{item_name : "Orange", item_index : "2"},
]
}
});
<div class="some_class" ba-repeat="{{ item :: named_collection}}">
<div>
<span>{{item.item_name}}</span>
<span>{{item.item_index}}</span>
</div>
</div>
Evaluates to
<div>
<div>
<span>Apple</span>
<span>1</span>
</div>
<div>
<span>Orange</span>
<span>2</span>
</div>
</div>
Filters are a way of defining rules to limit the number of items shown by the ba-repeat partial.
<ul ba-repeat="{{ i ~ i == 2 :: [1,2] }}">
<li>{{i}}</li>
</ul>
Evaluates to
<ul>
<li>2</li>
</ul>
dynamic = new BetaJS.Dynamics.Dynamic({
element: document.querySelector(".some_class"),
collections : {
named_collection: [
{item_name : "Apple", item_index : "1"},
{item_name : "Orange", item_index : "2"},
]
}
});
<div class="some_class" ba-repeat="{{ item ~ item.item_index == 1 :: named_collection}}">
<div>
<span>{{item.item_name}}</span>
<span>{{item.item_index}}</span>
</div>
</div>
Evaluates to
<div>
<div>
<span>Apple</span>
<span>1</span>
</div>
</div>
Sort is a way to define a sorting algorithm for collection in the ba-repeat partial.
var dynamic = new BetaJS.Dynamics.Dynamic({
element: root.get(0),
collections: {
items: [{test: "A"}, {test: "D"}, {test: "C"}, {test: "B"}]
}
});
dynamic.get("items").set_compare(function (x, y) {
return x.get("test") < y.get("test") ? 1 : -1;
});
<ul ba-repeat="{{ item :: items }}">
<li>{{item}}</li>
</ul>
Evaluates to
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
<li>D</li>
</ul>
ba-repeat-element is a special form of ba-repeat, it repeats the element it is placed on itself. The ba-repeat is the preferred partial due to performance.
<ul>
<li ba-repeat-element="{{ i :: [1,2] }}">{{i}}</li>
</ul>
Evaluates to
<ul>
<li>1</li>
<li>2</li>
</ul>
The ba-attrs Partial allows you to set initial property values for a dynamic from within its DOM scope.
<div class="some_class" ba-attrs="{{{foobar: 'xyz'}}}">
<ba-innertest ba-attrs="{{{tester: 'abc'}}}">
</ba-innertest>
</div>
<testouter>{{foobar}}</testouter><testinner>{{tester}}</testinner>
BetaJS.Dynamics.Dynamic.extend(null, {
template: '<outer>{{foobar}}</outer><inner>{{tester}}</inner>',
}).register("ba-innertest");
var dynamic = new BetaJS.Dynamics.Dynamic({
element: document.querySelector('.some_class')
});
dynamic.activate();
Will evaluate to
<div class="some_class">
<outer>abc</outer><inner></inner>
</div>
<testouter></testouter><testinner>xyz</testinner>
The ba-on partial executes the code passed into it when the specified DOM event is fired.
Will call an alert if somebody hovers the mouse over the element
<div ba-on:mouseover="alert('Hi')">Hover me<div>
The ba-return partial executes the code passed into it, or calls a function from the related dynamic, when the user presses the return/enter key when he is in the input field this is placed on.
Will call an alert pop-up displaying the text in the input field.
<input ba-return="alert({{input_value}})" value={{input_value}} />
BetaJS-UI is a library for enabling gestures and interactions such as drag and drop.
Interactions allow you to register particular user interactions like drag, drop, swipe, pinch, scroll etc. on DOM elements.
You can react to events emitted by the respective interactions.
The interaction system is completely independent of any UI/UX framework.
All interactions are configuered as follows:
var interaction = new InteractionClass(domElement, {
options
});
interaction.on("event", function () {
// React to particular interaction event.
});
You can also instantiate multiple interactions at once when providing a jQuery selector for multiple elements:
InteractionClass.multiple(jqueryElements, {
options
}, function (interaction) {
interaction.on("event", function () {
// React to particular interaction event.
});
});
The drag interaction allows the user to drag a dom element within constraints. This allows you to enable two types of drag interactions:
Finally, the drag interaction works well with the drop interaction, to fully implement a drag-and-drop-like system.
A standard drag, in which the element is being cloned (temporarily), can be instantiated as follows:
var drag = BetaJS.UI.Interactions.Drag(domElement, {
enabled : true,
clone_element: true
});
drag.on("move", function (event) {
event.actionable_modifier.csscls("focus", true);
event.modifier.csscls("unfocus", true);
});
The drop interaction allows you accept dropped elements originating from a drag interaction.
var drag = new BetaJS.UI.Interactions.Drag(dragSourceElement, {
droppable: true,
enabled : true,
clone_element: true,
remove_element_on_drop : true
});
drag.on("move", function (event) {
event.actionable_modifier.csscls("focus", true);
event.modifier.csscls("unfocus", true);
});
var drop = new BetaJS.UI.Interactions.Drop(dropTargetElement, {
enabled : true
});
drop.on("hover", function(dr) {
dr.modifier.css("border", "4px solid green");
});
drop.on("dropped", function(event) {
$("#drop").append(event.source.element.html());
});
The pinch interaction allows the user to zoom in/out within a particular dom element.
For this example, assume the following dom element:
<div class="pinch" style="width: 200px; height: 200px">
Pinch Me
</div>
We initialize the pinch interaction:
var pinch = new BetaJS.UI.Interactions.Pinch($(".pinch"), {
enabled: true
});
Once the user initiates a pinch, we need to react to it by resizing the underlying dom element, e.g. as follows:
pinch.on("pinch", function (details) {
$(".pinch").css("width", (parseInt($(".pinch").css("width"), 10) + details.delta_last.x) + "px");
$(".pinch").css("height", (parseInt($(".pinch").css("height"), 10) + details.delta_last.y) + "px");
});
The gesture allows you to react to particular touch / click gestures and kick off interactions based on a successful gesture.
The gesture system is implemented as a competitive state engine. That particularly enables you to register multiple gestures on DOM elements, competing with each other.
A gesture is registered on a DOM element as follows:
var gesture = new BetaJS.UI.Gestures.Gesture(domElement, BetaJS.UI.Gestures.defaultGesture({
mouse_up_activate: false,
wait_time: 750,
wait_activate: true,
disable_x: 10,
disable_y: 10,
enable_x: -1,
enable_y: -1
}));
This gesture, for instance, is activated by touching the particular dom element, waiting 750 ms and not moving more than 10 pixels both in horizontal and vertical drection.
You can react to a successful gesture or to an unsuccessful gesture as follows>
gesture.on("activate", function () {
// ...
});
gesture.on("deactivate", function () {
// ...
});
The most typical use case combines gestures with interactions. The interactions are configured slightly differently, to not automatically kick off once the user starts to interact with the DOM element. This part is now handled by the gesture.
Here is a typical example:
var drag = new BetaJS.UI.Interactions.Drag(domElement, {
enabled : true,
clone_element: true,
start_event: null
});
gesture.on("activate", drag.start, drag);
gesture.on("deactivate", drag.stop, drag);
Both the interaction and the gesture system are completely independent from any frontend system. That includes the dynamics system.
BetaJS UI contains a small optional bridging system that makes it particularly easy to apply both gestures and interactions to dom elements within the dynamics system, by applying partials to the respective dom elements:
<div ba-gesture:drag="{{{data: user_data, options: drag_gesture_options}}}"
ba-interaction:drag="{{{data: user_data, options: drag_interaction_options}}}">
</div>
There can be multiple gestures and interactions applied to a single dom element.
The value part of both the gesture and the interaction partials contain a (dynamically generated) JSON object with a data
parameter (which can be, for instance, an item from a ba-repeat
partial) and an options
parameter. While the options can be given explicitly as a JSON as well, it is recommended to outsource the options into the attributes of the dynamic:
BetaJS.Dynamics.Dynamic.activate({
element: document.body,
attrs: {
drag_gesture_options: {
mouse_up_activate: false,
wait_time: 750,
wait_activate: true,
disable_x: 10,
disable_y: 10,
enable_x: -1,
enable_y: -1,
interaction: "drag"
},
drag_interaction_options: {
type: "drag",
clone_element: true,
start_event: null,
events: {
"move": function (doodad, event) {
event.actionable_modifier.csscls("focus", true);
event.modifier.csscls("unfocus", true);
}
}
}
}
});
BetaJS-Flash is a Flash-JavaScript bridging framework
This framework allows you to access flash objects and functions without recompiling the Flash file itself.
The framework marshals and unmarshals creating and destroying Flash objects, calling methods and accessing attributes.
In order to be able to instantiate Flash objects and call methods on these objects, we need to create a Flash Registry
and register all the entities that we want to call:
var registry = new BetaJS.Flash.FlashClassRegistry();
registry.register("flash.media.Video", ["attachNetStream"]);
registry.register("flash.display.Sprite", ["addChild"]);
registry.register("flash.net.NetStream", ["play", "addEventListener"]);
registry.register("flash.net.NetConnection", ["connect", "addEventListener"]);
In this example, we register the classes flash.media.Video
, flash.display.Sprite
, flash.net.NetStream
and flash.net.NetConnection
,
and on those classes the instance methods attachNetStream
, addChild
, play
, addEventListener
and connect
.
It suffices to only register the methods you are intending to call.
Once the registry is defined, we need to add our Flash embedding to the DOM.
We assume a container element like the following one:
<div id='embed-here'></div>
We can then embed Flash as follows:
var embedding = new BetaJS.Flash.FlashEmbedding($("#embed-here").get(0), {
registry: registry,
wrap: true
}, {
flashFile: "betajs-flash/dist/betajs-flash.swf"
});
This call embeds a Flash embedding within the given container, using the registry
we defined previously, and linking to general Flash bridging file.
Before accessing the embedding, we have to wait for the ready event:
embedding.ready(function () {
// Execute Flash functions on the embedding.
});
Once the embedding is ready, we can instantiate objects, call methods and access properties as we normally would in Flash.
Note that you can only access objects and method defined in the registry.
var main = embedding.flashMain();
var stage = main.get("stage");
stage.set("scaleMode", "noScale");
stage.set("align", "TL");
var video = embedding.newObject("flash.media.Video", stage.get("stageWidth"), stage.get("stageHeight"));
main.addChildVoid(video);
Many Flash objects provide so-called Event Listeners
. The framework allows to create callbacks in JavaScript, that can be passed in as Event Listeners
:
var connection = embedding.newObject("flash.net.NetConnection");
var cb = embedding.newCallback(function () {
var stream = embedding.newObject("flash.net.NetStream", connection);
video.attachNetStreamVoid(stream);
stream.playVoid("movie.mp4");
});
connection.addEventListener("netStatus", cb);
connection.connectVoid(null);
BetaJS-Media is a JavaScript media framework
The video playback abstractions of this module allow cross-browser playback of videos.
This module only handles the playback itself without putting any UI/UX state machine on the player.
The cross-browser capabilities itself are based on HTML5 video playback with the abilitiy to fallback to Flash.
The Flash Player
component allows you to either polyfill an existing video element if necessary or to directly embed
a Flash polyfill into the DOM.
For polyfilling a video element, the polyfill call is invoked as follows:
<video autoplay loop poster="movie.png" style="width:50%" muted>
<source src="movie.mp4" type="video/mp4" />
</video>
BetaJS.Media.Player.FlashPlayer.polyfill($("video").get(0)).success(function (video) {
// video has been successfully polyfilled or kept they way it was if possible
});
You can even impose styles and typical attributes on the video element.
The Flash polyfill also allows you to use typical Flash protocols like RTMP
:
<video poster="movie.png">
<source src="rtmp://localhost:1935/vod/video.flv" type="video/flv" />
</video>
Instead of polyfilling an element, you can also explicitly embed the player:
<div id='element'>
</div>
var player = BetaJS.Media.Player.FlashPlayer.attach($("#element").get(0), {
autoplay : true,
loop : true,
muted: true,
poster : "movie.png",
sources: [{src: "movie.mp4"}]
});
The player
object itself understands the typical video
-element methods and emits the typical dom events as well.
Polyfilling with Flash helps to make videos play across more browser, but the access to the native video element
is not ideal and also not reliable in terms of emitted events and behaviour.
The VideoPlayerWrapper
class provides a completely uniform interface to all video playback systems:
<video>
</video>
BetaJS.Media.Player.VideoPlayerWrapper.create({
element: $("video").get(0),
poster: "movie.png",
source: "movie.mp4",
//forceflash: true
}).success(function (instance) {
// instance.play();
// instance.pause();
// instance.enterFullscreen();
}).error(function (error) {
});
The video playback abstractions of this module allow cross-browser playback of videos.
Currently, the system only allows for WebRTC recording.
This module only handles the recording itself without putting any UI/UX state machine on the recorder.
The WebRTC Recorder
abstraction is based on MediaRecorder
on browsers that support it and manual encoding to webm
on all others.
Given a video
element in the DOM, it is initialized as follows:
var view = BetaJS.Media.WebRTC.RecorderWrapper.create({
video: $("video").get(0)
});
view.on("bound", function (stream) {
view.startRecord();
...
view.stopRecord();
});
view.on("data", function (video_blob, audio_blob) {
// Handle data
});
view.bindMedia();
BetaJS-Media-Components is a JavaScript media UI components framework
Our video player is a dynamic component named ba-videoplayer
.
Its main parameters are poster
and source
, specifying the poster
image as well as the video source
file.
Depending on the browser, device and video format at hand, the system will automatically determine whether HTML 5 video can used or whether to fall back to Flash.
If you want to use Flash, make sure to initialize the Flash bridging framework of BetaJS:
BetaJS.Flash.options = {
flashFile: "http://betajs.com/betajs-flash.swf" // location of your flash file
};
As always with the dynamics system, there are two ways to to activate it.
You can embed it somewhere in the HTML dom and active the dynamic system on of its parent nodes (here: on the document body):
<ba-videoplayer ba-loop ba-poster="..." ba-source="...">
</ba-videoplayer>
BetaJS.Dynamics.Dynamic.activate();
The other way is to define a parent dom node and embed it via JavaScript:
<div id='parent'></div>
var player = BetaJS.MediaComponents.VideoPlayer.Dynamics.Player({
element: document.getElementById('parent'),
attrs: {
poster: "...",
source: "..."
}
});
player.activate();
The particular sizing of the player can be set by adding normal css classes or styles to the ba-videoplayer
element.
The advanced options allow you to configure the player even more precisely:
For example:
<ba-videoplayer ba-stretch ba-nofullscreen ba-loop ba-poster="..." ba-source="...">
</ba-videoplayer>
Themes are a combination of CSS classes, HTML templates and potentially subdynamics.
The default
theme is automatically applied to the player if not otherwise specified and looks as follows:
The modern
theme can be manually applied:
The system supports multiple locales, fully independent of the selected theme.
The current locales is auto-detected by analysing the browser at hand, but can also be overwritten explicitly:
BetaJS.MediaComponents.Assets.strings.setLocale("de");
Whenever a particular string asset doesn't exist in the specified locale, the system automatically falls back to the default string in English.
You can also define your own locale:
BetaJS.MediaComponents.Assets.strings.register({
"ba-videoplayer-playbutton.tooltip": "Klaki ludi video."
}, ["language:esperanto"]);
BetaJS-Debug is a library for debugging BetaJS-based applications.
The debugging profiles allows to hook single or all methods of a BetaJS object or class.
This is particularly helpful for understanding how often a particular method, object or class is being called.
If you want to hook class method foobar
of class C
, you can write:
var hook = BetaJSDebug.Hooks.hookMethod("foobar", C, function (method, Cls, args, callContext) {
// called when the method foobar is being called.
}, function (method, Cls, args, callContext, result) {
// called after the method foobar was called
});
After you're done, you can unregister the hook as follows:
BetaJSDebug.Hooks.unhookMethod(hook);
You can also hook instance methods. Assume that class C
also has an instance method method
:
var hook = BetaJSDebug.Hooks.hookPrototypeMethod("method", C, function (method, Cls, args, callContext) {
// called when the method foobar is being called.
}, function (method, Cls, args, callContext, result) {
// called after the method foobar was called
});
Finally, you can also hook all class / instance methods:
var hooks = BetaJSDebug.Hooks.hookMethods(C, function (method, Cls, args, callContext) {
// called when the method foobar is being called.
}, function (method, Cls, args, callContext, result) {
// called after the method foobar was called
});
var hooks = BetaJSDebug.Hooks.hookPrototypeMethods(C, function (method, Cls, args, callContext) {
// called when the method foobar is being called.
}, function (method, Cls, args, callContext, result) {
// called after the method foobar was called
});
BetaJSDebug.Hooks.unhookMethods(hooks);
Profiling instantiation allows you to find memory leaks, by counting how many instances are present at a particular time.
To start monitoring instantiation of a particular base class BaseClass
, we create a sub class filter
(more on that in a second) and set up the debugger:
var debugger = BetaJSDebug.Instances.monitorInstances(BaseClass, filter);
Once you're done debugging, you can unregister the debugger as follows:
BetaJSDebug.Instances.unmonitorInstances(debugger);
The easiest way to inspect the instance monitor at any point, is to convert the data to an HTML table (which can be added to the DOM, e.g. using jQuery):
BetaJSDebug.Instances.toHTMLTable(debugger);
Filters allow you to restrict the sub classes of the BaseClass
that you want to track. You can create basic filters and then use compound filters to combine them:
BetaJSDebug.Instances.allFilter()
: matches all classesBetaJSDebug.Instances.andFilter(filters)
: matches if all filters in the array matchBetaJSDebug.Instances.orFilter(filters)
: matches if one filter in the array matchesBetaJSDebug.Instances.regexFilter(regex)
: matches if the class name matches the regular expressionBetaJSDebug.Instances.ancestryFilter(filter)
: matches if the class or any of the ancestors matches the filterBuild BetaJS templates.
In your project's Gruntfile, add a section named betajs_templates
to the data object passed into grunt.initConfig()
.
The namespace of each betajs_templates
namespace must be specified. See
any of the examples for guidance on specifying the namespace option.
grunt.initConfig({
betajs_templates: {
dist: {
files: {
"dest/betajs-templates.js": [
"src/my_first_template.html",
"src/my_second_template.html",
"src/my_last_templates.html"
]
},
options: {
namespace: 'App.Templates'
}
},
},
});
Naturally, it is possible to specify a different namespace for each subtask.
Multiple namespaces for different subtasks can be seen in the example below.
grunt.initConfig({
betajs_templates: {
dashboard: {
files: {
"dest/betajs-dashboard-templates.js": [
"dashboard/*.html",
]
},
options: {
namespace: 'App.Dashboard'
}
},
homepage: {
files: {
"dest/betajs-homepage-templates.js": [
"homepage/*.html"
]
},
options: {
namespace: 'App.Homepage'
}
}
}
});
Build BetaJS documentations based on JSDOC.
This plugin requires Grunt ~0.4.5
If you haven't used Grunt before, be sure to check out the Getting Started guide, as it explains how to create a Gruntfile as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:
npm install grunt-betajs-docs-compile --save-dev
Once the plugin has been installed, it may be enabled via the jsdoc plugin inside your Gruntfile with this line of JavaScript:
grunt.loadNpmTasks('grunt-jsdoc');
jsdoc : {
dist : {
src : sources,
options : {
destination : '../',
template : 'node_modules/grunt-betajs-docs-compile',
configure : './jsdoc.conf.json',
tutorials : './tutorials'
}
}
}
Additional configuration can be done in the jsdoc.conf.json. It might looks like this:
{
"tags": {
"allowUnknownTags": true
},
"plugins": ["plugins/markdown"],
"templates": {
"cleverLinks": false,
"monospaceLinks": false,
"dateFormat": "ddd MMM Do YYYY",
"outputSourceFiles": true,
"outputSourcePath": true,
"systemName": "FooBar",
"footer": "",
"copyright": "MIT License",
"navType": "vertical",
"theme": "cerulean",
"linenums": true,
"collapseSymbols": false,
"inverseNav": true,
"highlightTutorialCode": true,
"protocol": "fred://"
},
"markdown": {
"parser": "gfm",
"hardwrap": true
}
}
This is mostly preserved and copied from Ink-Docstrap. Additionally, you can use the following optional arguments:
{
...
"templates": {
...
"template": "my/local/template/directory",
"emptyTutorials": false || true,
"singleTutorials": false || true,
"singleModules": false || true
},
...
"pages": {
"about": {
"title": "About",
"source": "./about.md"
}
}
}
BetaJS-Codemirror is a Codemirror Plugin for the BetaJS Framework.
The codemirror module registers a wrapper for the Codemirror Editor via the dynamics system. You can instantiate it as follows:
<ba-codemirror ba-trim ba-language='html'>
<div>
<h1>H1 None</h1>
<br />
<strong>Strong 1</strong> Text 1 <code>Code 1</code><br />
<code style="text-decoration: underline" >Code 2</code><strong> Strong 2</strong>
<div style="font-size: 24px; font-family: Helvetica;">Text 2</div>
</div>
</ba-codemirror>
BetaJS.Dynamics.Dynamic.activate();
BetaJS-Richeditor is a rich editor plugin based on content editable using the BetaJS Framework.
The richeditor module registers a rich editor component via the dynamics system. You can instantiate it as follows:
<ba-richeditor>
<div>
<h1>H1 None</h1>
<br />
<strong>Strong 1</strong> Text 1 <code>Code 1</code><br />
<u>Code 2</u><strong> Strong 2</strong>
<div style="font-size: 24px; font-family: Helvetica;">Text 2</div>
</div>
</ba-richeditor>
BetaJS.Dynamics.Dynamic.activate();