This is a set of UI components inspired by the iOS UIKit Framework. The UIKit provides crucial infrastructure to construct and manage web apps.
$ npm install backbone-uikit --save
UIView
UIButton
UILabel
UITextField
UITextView
UISegmentedControl
UIStepper
UIImageView
UINavigationBar
UITabBar
UIScrollView
UIActivityIndicatorView
UIAccordion
UISelect
UICheckbox
The UIView class defines a rectangular area on the screen and interfaces for content in that area.
var UIKit = require('backbone-uikit');
var AppView = UIKit.UIView.extend({
el: 'body',
render: function() {
this.$el.empty();
// your code
return this;
}
});
module.exports = new AppView();
Views can embed other views and create sophisticated visual hierarchies.
This creates a parent-child relationship between the view being embedded (known as the subview) and
the parent view performing the embedding (known as the superview).
Normally, a subview’s visible area is not clipped to the bounds of its superview,
but you can use the overflow CSS property to alter that behavior.
A parent view may contain any number of subviews
but each subview has only one superview
.
To add a subview to another view, you apply the method addSubview:
render: function() {
this.$el.empty(); // Optional
this.addSubview(new UIKit.UIView({
class: 'my-view'
}));
return this; // Required by Backbone
}
To remove a view with all the subviews
and its el
from the DOM,
and calls stopListening to remove any bound events that the view has listenTo'd, you can employ the destroy method:
var anotherView = new UIKit.UIView({
class: 'my-view'
});
// ...
anotherView.destroy();
UIView.prototype.initialize()
do a lot of useful things for you:
touchstart
<=> mousedown
touchend
<=> mouseup
transitionEndHandler
on 'webkitTransitionEnd'
event
initialize: function(options) {
UIView.prototype.initialize.apply(this, [options]);
// your code
}
Sometimes you need to determine the actual size of the view. This is possible only after building the DOM. For this purpose it is convenient to use the following approach.
render: function() {
this.$el.empty();
setTimeout(() => {
this.layout(0); // Call it as you wish
}, 0);
return this;
},
layout: function() {
var rect = this.size(); // For example: { width: 100, height: 200 }
}
A view can handle touch events by jGestures and other events defined by jQuery.
events: {
// Touch Events and Gestures by jGestures
touchstart: 'touchstartHandler',
touchend: 'touchendHandler',
tapone: 'taponeHandler',
swipemove: 'swipemoveHandler',
pinch: 'pinchHandler',
// Mouse Events by jQuery
mousedown: 'mousedownHandler',
mouseup: 'mouseupHandler',
mousemove: 'mousemoveHandler',
mouseover: 'mouseoverHandler',
mouseout: 'mouseoutHandler',
mouseenter: 'mouseenterHandler',
mouseleave: 'mouseleaveHandler'
}
touchstartHandler: function(event) {},
touchendHandler: function(event) {},
taponeHandler: function(event) {},
swipemoveHandler: function(event, obj) {}
userInteractionEnabled: false
superview: null,
subviews: [],
addSubview: function(view [, '.element' | $element]) {}
bringSubviewToFront: function() {},
sendSubviewToBack: function() {},
viewDidAppear: function() {},
viewDidDisappear: function() {}
// Handle availability
// use '.ui-dis' in CSS
disabled: false,
disable: function() {},
enable: function() {},
// Handler visibility
// use '.ui-hid' in CSS
hidden: false,
hide: function() {},
show: function() {},
// Handle selection
// Use '.ui-sel' in CSS
selected: false,
deselect: function() {},
select: function() {}
To achieve the highest frame rate separate animated inline styles with the static ones from the CSS files.
calculateAnimatedStyles: function(animated) {},
applyAnimatedStyles: function(animated) {},
transitionEndHandler: function(event) {
event.stopPropagation();
}
More sophisticated content is presented by subclassing UIView and implementing the necessary drawing and event-handling.
this.addSubview(new UIKit.UIButton({
class: 'backButton',
label: 'Back',
action: () => {
this.goBack();
}
}));
this.addSubview(new UIKit.UILabel({
text: 'Text'
}));
UILabel
can display some data from a model and watch changes.
var model = new Backbone.Model({
data: 1
});
this.addSubview(new UIKit.UILabel({
model: model,
attribute: 'data'
}));
this.addSubview(new UIKit.UITextField({
class: 'my-text-field-01',
name: 'test',
placeholder: 'One line ...',
changeHandler: function(event) {
console.log('event.data.value = ', event.data.value);
console.log('this.value = ', this.value);
}
}));
var user = new Backbone.Model({
phoneNumber: '+12345678910'
});
this.addSubview(new UIKit.UITextField({
class: 'phone-text-field',
name: 'phone',
type: 'tel',
autocomplete: 'tel',
placeholder: 'Your phone',
model: user,
attribute: 'phoneNumber'
}));
this.addSubview(new UIKit.UITextView({
class: 'my-text-view',
placeholder: 'Let us know more ...',
text: ''
}));
this.addSubview(new UIKit.UISegmentedControl({
items: [{
label: 'First'
}, {
label: 'Second'
}, {
label: 'Third'
}],
changeHandler: (index) => {
console.log(index);
}
}));
this.addSubview(new UIKit.UIStepper({
class: 'my-stepper',
value: 0,
minimumValue: 0,
maximumValue: 1000,
stepValue: 1,
autorepeat: false
}));
var model = new Backbone.Model({
number: 1
});
this.addSubview(new UIKit.UIStepper({
model: dataModel,
attribute: 'number',
minimumValue: 0,
maximumValue: 10,
changeHandler: function(value) {
console.log('changeHandler, value = ', value);
}
}));
this.addSubview(new UIKit.UIImageView());
var submitBtn = new UIButton({
label: 'Submit',
action: function() {}
});
this.addSubview(new UINavigationBar({
leftBarItems: [],
centerBarItems: [new UILabel({ text: this.title })],
rightBarItems: [submitBtn]
}));
this.addTabBar(new UIKit.UITabBar({
selectedIndex: 0
}));
var scrollView = new UIScrollView({
class: 'my-scroll-view',
minimumScale: 0.08,
maximumScale: 3,
testHandler: function() {
// Your code
}
});
var contentView = new ContentView();
this.addSubview(scrollView);
scrollView.addSubview(contentView);
this.addSubview(new UIKit.UIActivityIndicatorView());
this.addSubview(new UIKit.UIAccordion({
class: 'my-accordion',
openedIndex: 2,
buttons: [
new MyAccordionButtonView({
title: 'First'
}),
new MyAccordionButtonView({
title: 'Second'
}),
new MyAccordionButtonView({
title: 'Third'
})
],
items: [
new UIView({
class: 'first-acc-view'
}),
new UIView({
class: 'second-acc-view'
}),
new UIView({
class: 'third-acc-view'
})
]
}));
var MySelectItemView = require('./MySelectItemView');
var collection = new Collection();
collection.add([{
title: 'First',
description: 'First model'
}, {
title: 'Second',
description: 'Second model'
}, {
title: 'Third',
description: 'Third model'
}]);
this.addSubview(new UIKit.UISelect({
class: 'my-select',
listClass: 'my-select-list',
// label: 'Select ...',
// appearance: 'up',
collection: collection,
// disabled: true,
ItemView: MySelectItemView,
changeHandler: function() {
console.log(this.selectedIndex);
}
}));
this.addSubview(new UIKit.UICheckbox({
name: 'my-checkbox',
checked: false
}));
alert()
confirm()
prompt()
actionSheet()
modal()
UIKit.alert({
title: 'Title',
message: 'This is a message.',
okButtonLabel: 'OK'
});
UIKit.confirm({
title: 'Title',
message: 'This is a message.',
cancelButtonLabel: 'Cancel',
okButtonLabel: 'OK'
})
.done(function() {})
.fail(function() {});
UIKit.prompt({
title: 'Title',
message: 'This is a message.',
placeholder: 'This is a placeholder',
value: '',
cancelButtonLabel: 'Cancel',
okButtonLabel: 'OK'
})
.done(function(value) {
console.log('ok, value = ', value);
})
.fail(function() {});
UIKit.actionSheet({
title: 'Title',
actions: actions,
cancelButtonLabel: 'Cancel'
})
.done(function(data) {
console.log('ok, index = ', data);
})
.fail(function() {
console.log('cancel');
});
UIKit.modal({
contentView: new SomeView()
});
How to close the modal view from the content view:
this.superview.hide();
All the modal dialogs trigger event 'uikit-modal'
on Backbone
with a promised view as a data.
You can use it to handle the view in various situations.
// appModel
this.listenTo(Backbone, 'uikit-modal', (view) => {
this.set({
modal: view
});
});
// someRouter
if (appModel.get('modal')) {
switch (appModel.get('modal').state()) {
case 'pending':
appModel.get('modal').destroy();
break;
case 'resolved':
case 'rejected':
appModel.set({
modal: null
});
break;
default:
}
}
$ npm run dev
When the library is built, it excludes jQuery, Underscore, Backbone and jGestures from the
built library. Consumers of the built library will provide all the dependencies for the library.
If the consumer uses an AMD loader, then the built
file will ask for jquery
, underscore
, backbone
and jgestures
as AMD dependencies. If the consumer
just uses browser globals and script tags, the library will grab the $
, _
, Backbone
and jGestures
global variables and use them for the dependencies.
The built library also does not include require.js in the file, but instead uses almond, a small AMD API implementation, that allows the built file's internal modules to work. These internal modules and this version of almond are not visible outside the built file, just used internally by the built file for code organization and referencing.
This project creates a library called uikit.js and a file uikit.css in the dist folder.
$ npm run build
This will generate the built files `dist/uikit.js` and `dist/uikit.css`.
Test the built file by running these files:
You can tell them this library can be used with other AMD modules, or it can be used in a project that uses browser globals and HTML script tags.
Copyright © 2017 Andrey Aleshkov
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.