Skip to content

Commit

Permalink
Changelog:
Browse files Browse the repository at this point in the history
* added first ES6/ES7 code draft to `lib/src` folder, the following changes apply
* all components have `destroy()` method
* recent fixes to allow re-init are included (on re-init we trigger `destroy()` before anything else)
* more clear init objects ( in essence we're talking `{chaos,options,methods}` -> `{element,target,options,methods}` )
* removed manual tokenization, resulting in easier long term maintenance, the min files will be larger while gzip files should be 7-10% smaller, now @mustafa0x should be happy #269
* replaced `getClosest()` utility with native `element.closest(selector)`
* Popover and Tooltip now both use `options.template` and make use of attributes like `data-title` to fill in the template markup
* Popover and Tooltip can work simultaneously with other `data-toggle` components via `data-tip="tooltip"` and `data-tip="popover"` attributes (Modal,Dropdown,Tab,Collapse)

TO DO (priority ordered):
* debug and fix all issues resulted in removal of manual tokenization or other module import/link related issues
* evaluate and discuss code structure to find opportunities for new features and code quality improvements (constructor, private & public methods, etc)
* Toast component: aren't `destroy()` and `dispose()` methods supposed to do the same?
* we need a new ES6/ES7 friendly `initCallback`, we might also need an ES6/ES7 friendly `removeDataAPI` for all components, the opposite of `initCallback`, a possible draft at `src/util/callbacks.js`
* we need a new bundle builder (lib/bundle.js) to do ES6/ES7 `index.js` -> ES5 `bootstrap-native.js` compilation + uglify work, that's where YOU come in (rollup, babel, promise, whatever you need, bring it)
* we need a full documentation for V4 page, the future main page of the project
* wiki updates


@cvaize and everyone interested in this thread please join discussion here #306

Happy New Year!
  • Loading branch information
thednp committed Dec 31, 2019
1 parent d5a6fda commit 01a1d50
Show file tree
Hide file tree
Showing 25 changed files with 2,303 additions and 9 deletions.
10 changes: 6 additions & 4 deletions dist/bootstrap-native-v4.js
Original file line number Diff line number Diff line change
Expand Up @@ -1152,6 +1152,7 @@

// bind
var self = this,

// strings
component = 'modal',
staticString = 'static',
Expand Down Expand Up @@ -1466,7 +1467,7 @@
: navbarFixedTop ? navbarFixedTop
: navbarFixedBottom ? navbarFixedBottom
: modal ? modal : DOC[body];

// title and content
var titleString, contentString,
placementClass = 'bs-' + component+'-'+self[placement];
Expand Down Expand Up @@ -1530,8 +1531,8 @@
popoverBody = queryElement('.'+bodyClass,popover);

// fill the template with content from data attributes
!!titleString && popoverHeader && (popoverHeader[innerHTML] = titleString.trim());
!!contentString && popoverBody && (popoverBody[innerHTML] = contentString.trim());
titleString && popoverHeader && (popoverHeader[innerHTML] = titleString.trim());
contentString && popoverBody && (popoverBody[innerHTML] = contentString.trim());
}

//append to the container
Expand Down Expand Up @@ -2007,6 +2008,7 @@
};
self.destroy = function() {
self.hide();
clearTimeout(timer);
off(element, clickEvent, self.hide);
delete element[stringToast];
};
Expand Down Expand Up @@ -2095,7 +2097,7 @@
},
createToolTip = function() {
titleString = getTitle(); // read the title again
if ( !!titleString ) { // invalidate, maybe markup changed
if ( titleString ) { // invalidate, maybe markup changed
// create tooltip
tooltip = DOC[createElement](div);

Expand Down
2 changes: 1 addition & 1 deletion dist/bootstrap-native-v4.min.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lib/V4/modal-native.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ var Modal = function(element, options) { // element can be the modal/triggering

// bind
var self = this,

// strings
component = 'modal',
staticString = 'static',
Expand Down
6 changes: 3 additions & 3 deletions lib/V4/popover-native.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ var Popover = function( element, options ) {
: navbarFixedTop ? navbarFixedTop
: navbarFixedBottom ? navbarFixedBottom
: modal ? modal : DOC[body];

// title and content
var titleString, contentString,
placementClass = 'bs-' + component+'-'+self[placement];
Expand Down Expand Up @@ -128,8 +128,8 @@ var Popover = function( element, options ) {
popoverBody = queryElement('.'+bodyClass,popover);

// fill the template with content from data attributes
!!titleString && popoverHeader && (popoverHeader[innerHTML] = titleString.trim());
!!contentString && popoverBody && (popoverBody[innerHTML] = contentString.trim());
titleString && popoverHeader && (popoverHeader[innerHTML] = titleString.trim());
contentString && popoverBody && (popoverBody[innerHTML] = contentString.trim());
}

//append to the container
Expand Down
1 change: 1 addition & 0 deletions lib/V4/toast-native.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ var Toast = function( element,options ) {
};
self.destroy = function() {
self.hide();
clearTimeout(timer);
off(element, clickEvent, self.hide);
delete element[stringToast];
};
Expand Down
2 changes: 1 addition & 1 deletion lib/V4/tooltip-native.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ var Tooltip = function( element,options ) {
},
createToolTip = function() {
titleString = getTitle(); // read the title again
if ( !!titleString ) { // invalidate, maybe markup changed
if ( titleString ) { // invalidate, maybe markup changed
// create tooltip
tooltip = DOC[createElement](div);

Expand Down
5 changes: 5 additions & 0 deletions lib/bundle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
var bundle = require('./src/index');
// var Alert = require('./src/alert-native');

console.log(bundle)
// console.log(Alert)
79 changes: 79 additions & 0 deletions lib/src/alert-native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@

/* Native JavaScript for Bootstrap 4 | Alert
-------------------------------------------*/

// IMPORTS
import { supports } from './util/callbacks.js';
import { hasClass, removeClass } from './util/class.js';
import { bootstrapCustomEvent, on, off } from './util/event.js';
import { queryElement } from './util/selector.js';
import { emulateTransitionEnd } from './util/transition.js';

// ALERT DEFINITION
// ================
export default class Alert {

constructor (element) {

// initialization element
element = queryElement(element);

// reset on re-init
element.Alert && element.Alert.destroy();

// bind, target alert, duration and stuff
const self = this,

// custom events
closeCustomEvent = bootstrapCustomEvent('close','alert'),
closedCustomEvent = bootstrapCustomEvent('closed','alert'),

// handlers
triggerHandler = () => {
hasClass(alert,'fade') ? emulateTransitionEnd(alert,transitionEndHandler) : transitionEndHandler();
},
clickHandler = e => {
alert = e.target.closest(`.alert`);
element = queryElement(`[data-dismiss="alert"]`,alert);
element && alert && (element === e.target || element.contains(e.target)) && self.close();
},
transitionEndHandler = () => {
off(element, 'click', clickHandler); // detach it's listener
alert.parentNode.removeChild(alert);
dispatchCustomEvent.call(alert,closedCustomEvent);
};

// public method
self.close = () => {
if ( alert && element && hasClass(alert,'show') ) {
dispatchCustomEvent.call(alert,closeCustomEvent);
if ( closeCustomEvent.defaultPrevented ) return;
self.destroy();
}
}

self.destroy = () => {
removeClass(alert,'show');
alert && triggerHandler();
off(element, 'click', clickHandler);
delete element.Alert;
}

// init
if ( !element.Alert ) { // prevent adding event handlers twice
on(element, 'click', clickHandler);
}

// find the parent alert
let alert = element.closest(`.alert`);

// store init object within target element
self.element = element;
element.Alert = self;
}
}

// ALERT DATA API
// ==============
supports.push(['Alert', Alert, '[data-dismiss="alert"]']);

146 changes: 146 additions & 0 deletions lib/src/button-native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@

/* Native JavaScript for Bootstrap 4 | Button
---------------------------------------------*/

// IMPORTS
import { supports } from './util/callbacks.js';
import { hasClass, addClass, removeClass } from './util/class.js';
import { bootstrapCustomEvent, on, off } from './util/event.js';
import { queryElement, getElementsByClassName } from './util/selector.js';

// BUTTON DEFINITION
// ===================
export default class Button {

constructor(element){

// initialization element
element = queryElement(element);

// reset on re-init
element.Button && element.Button.destroy();

// constant
let toggled = false; // toggled makes sure to prevent triggering twice the change.bs.button events

// bind
const self = this,

// changeEvent
changeCustomEvent = bootstrapCustomEvent('change', 'button'),

// private methods
keyHandler = e => {
const key = e.which || e.keyCode;
key === 32 && e.target === document.activeElement && toggle(e);
},

preventScroll = e => {
const key = e.which || e.keyCode;
key === 32 && e.preventDefault();
},

toggle = e => {
const label = e.target.tagName === 'LABEL' ? e.target : e.target.parentNode.tagName === 'LABEL' ? e.target.parentNode : null; // the .btn label

if ( !label ) return; //react if a label or its immediate child is clicked

const // all the button group buttons
labels = getElementsByClassName(label.parentNode,'btn'),
input = label.getElementsByTagName('INPUT')[0];

if ( !input ) return; // return if no input found

dispatchCustomEvent.call(input, changeCustomEvent); // trigger the change for the input
dispatchCustomEvent.call(element, changeCustomEvent); // trigger the change for the btn-group

// manage the dom manipulation
if ( input.type === 'checkbox' ) { //checkboxes
if ( changeCustomEvent.defaultPrevented ) return; // discontinue when defaultPrevented is true

if ( !input.checked ) {
addClass(label,'active');
input.getAttribute('checked');
input.setAttribute('checked','checked');
input.checked = true;
} else {
removeClass(label,'active');
input.getAttribute('checked');
input.removeAttribute('checked');
input.checked = false;
}

if (!toggled) { // prevent triggering the event twice
toggled = true;
}
}

if ( input.type === 'radio' && !toggled ) { // radio buttons
if ( changeCustomEvent.defaultPrevented ) return;
// don't trigger if already active (the OR condition is a hack to check if the buttons were selected with key press and NOT mouse click)
if ( !input.checked || (e.screenX === 0 && e.screenY == 0) ) {
addClass(label,'active');
addClass(label,'focus');
input.setAttribute('checked','checked');
input.checked = true;

toggled = true;
for (let i = 0, ll = labels.length; i<ll; i++) {
const otherLabel = labels[i], otherInput = otherLabel.getElementsByTagName('INPUT')[0];
if ( otherLabel !== label && hasClass(otherLabel,'active') ) {
dispatchCustomEvent.call(otherInput, changeCustomEvent); // trigger the change
removeClass(otherLabel,'active');
otherInput.removeAttribute('checked');
otherInput.checked = false;
}
}
}
}
setTimeout( () => { toggled = false; }, 50 );
},
focusHandler = e => {
addClass(e.target.parentNode,'focus');
},
blurHandler = e => {
removeClass(e.target.parentNode,'focus');
},
toggleEvents = action => {
action( element, 'click', toggle );
action( element, 'keyup', keyHandler ), action( element, 'keydown', preventScroll );

const allBtns = getElementsByClassName(element, 'btn');
for (let i=0; i<allBtns.length; i++) {
const input = allBtns[i].getElementsByTagName('INPUT')[0];
action( input, 'focus', focusHandler), action( input, 'blur', blurHandler);
}
};

// public method
self.destroy = () => {
toggleEvents(off);
delete element.Button;
}

// init
if ( !element.Button ) { // prevent adding event handlers twice
toggleEvents(on);
}

// activate items on load
const labelsToACtivate = getElementsByClassName(element, 'btn'), lbll = labelsToACtivate.length;
for (let i=0; i<lbll; i++) {
!hasClass(labelsToACtivate[i],'active')
&& queryElement('input:checked',labelsToACtivate[i])
&& addClass(labelsToACtivate[i],'active');
}

// associate target with init object
self.element = element;
element.Button = self;
}
}

// BUTTON DATA API
// =================
supports.push( [ 'Button', Button, '[data-toggle="buttons"]' ] );

Loading

0 comments on commit 01a1d50

Please sign in to comment.