]> git.immae.eu Git - perso/Immae/Projets/packagist/ludivine-ckeditor-component.git/blob - sources/samples/js/sf.js
Validation initiale
[perso/Immae/Projets/packagist/ludivine-ckeditor-component.git] / sources / samples / js / sf.js
1 /**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5 /* exported SF */
6
7 'use strict';
8
9 var SF = ( function() {
10 var SF = {};
11
12 SF.attachListener = function( elem, evtName, callback ) {
13 if ( elem.addEventListener ) {
14 elem.addEventListener( evtName, callback, false );
15 } else if ( elem.attachEvent ) {
16 elem.attachEvent( 'on' + evtName , function() {
17 callback.apply( elem, arguments );
18 } );
19 } else {
20 throw new Error( 'Could not attach event.' );
21 }
22 };
23
24 SF.indexOf = ( function() {
25 var indexOf = Array.prototype.indexOf;
26
27 if ( indexOf === 'function' ) {
28 return function( arr, elem ) {
29 return indexOf.call( arr, elem );
30 };
31 } else {
32 return function( arr, elem ) {
33 var max = arr.length;
34
35 for ( var i = 0; i < max; i++ ) {
36 if ( arr[ i ] === elem ) {
37 return i;
38 }
39 }
40
41 return -1;
42 };
43 }
44
45 }() );
46
47 SF.accept = function( node, visitor ) {
48 var children;
49
50 // Handling node as a node and array
51 if ( node.children ) {
52 children = node.children;
53
54 visitor( node );
55 } else if ( typeof node.length === 'number' ) {
56 children = node;
57 }
58
59 var i = children ? ( children.length || 0 ) : 0;
60 while ( i-- ) {
61 SF.accept( children[ i ], visitor );
62 }
63 };
64
65 SF.getByClass = ( function( ) {
66 var getByClass = document.getElementsByClassName;
67 if ( typeof getByClass === 'function' ) {
68 return function( root, className ) {
69 if ( typeof root === 'string' ) {
70 className = root;
71 root = document;
72 }
73
74 return getByClass.call( root, className );
75 };
76 }
77
78 return function( root, className ) {
79 if ( typeof root === 'string' ) {
80 className = root;
81 root = document.getElementsByTagName( 'html' )[ 0 ];
82 }
83 var results = [];
84
85 SF.accept( root, function( elem ) {
86 if ( SF.classList.contains( elem, className ) ) {
87 results.push( elem );
88 }
89 } );
90
91 return results;
92 };
93 }() );
94
95 SF.classList = {};
96
97 SF.classList.add = function( elem, className ) {
98 var classes = parseClasses( elem );
99 classes.push( className );
100
101 elem.attributes.setNamedItem( createClassAttr( classes ) );
102 };
103
104 SF.classList.remove = function( elem, className ) {
105 var classes = parseClasses( elem, className ),
106 foundAt = SF.indexOf( classes, className );
107
108 if ( foundAt === -1 ) {
109 return;
110 }
111
112 classes.splice( foundAt, 1 );
113 elem.attributes.setNamedItem( createClassAttr( classes ) );
114 };
115
116 SF.classList.contains = function( elem, className ) {
117 return findIndex( elem, className ) !== -1;
118 };
119
120 SF.classList.toggle = function( elem, className ) {
121 this.contains( elem, className ) ? this.remove( elem, className ) : this.add( elem, className );
122 };
123
124 function findIndex( elem, className ) {
125 return SF.indexOf( parseClasses( elem ), className );
126 }
127
128 function parseClasses( elem ) {
129 var classAttr = elem.attributes ? elem.attributes.getNamedItem( 'class' ) : null;
130
131 return classAttr ? classAttr.value.split( ' ' ) : [];
132 }
133
134 function createClassAttr( classesArray ) {
135 var attr = document.createAttribute( 'class' );
136
137 attr.value = classesArray.join( ' ' );
138
139 return attr;
140 }
141
142 return SF;
143 }() );
144
145 /* global SF, picoModal */
146
147 'use strict';
148
149 ( function() {
150 // Purges all styles in passed object.
151 function purgeStyles( styles ) {
152 for ( var i in styles ) {
153 delete styles[ i ];
154 }
155 }
156
157 SF.modal = function( config ) {
158 // Modal should use the same style set as the rest of the page (.content component).
159 config.modalClass = 'modal content';
160 config.closeClass = 'modal-close';
161
162 // Purge all pre-defined pico styles. Use the lessfile instead.
163 config.modalStyles = purgeStyles;
164
165 // Close button styles are customized via lessfile.
166 config.closeStyles = purgeStyles;
167
168 var userDefinedAfterCreate = config.afterCreate,
169 userDefinedAfterClose = config.afterClose;
170
171 // Close modal on ESC key.
172 function onKeyDown( event ) {
173 if ( event.keyCode == 27 ) {
174 modal.close();
175 }
176 }
177
178 // Use afterCreate as a config option rather than function chain.
179 config.afterCreate = function( modal ) {
180 userDefinedAfterCreate && userDefinedAfterCreate( modal );
181
182 window.addEventListener( 'keydown', onKeyDown );
183 };
184
185 // Use afterClose as a config option rather than function chain.
186 config.afterClose = function( modal ) {
187 userDefinedAfterClose && userDefinedAfterClose( modal );
188
189 window.removeEventListener( 'keydown', onKeyDown );
190 };
191
192 var modal = new picoModal( config )
193 .afterCreate( config.afterCreate )
194 .afterClose( config.afterClose );
195
196 return modal;
197 };
198 } )();
199 'use strict';
200
201 ( function() {
202 // All .tree-a elements in DOM.
203 var expanders = SF.getByClass( 'toggler' );
204
205 var i = expanders.length;
206 while ( i-- ) {
207 var expander = expanders[ i ];
208
209 SF.attachListener( expander, 'click', function() {
210 var containsIcon = SF.classList.contains( this, 'icon-toggler-expanded' ) || SF.classList.contains( this, 'icon-toggler-collapsed' ),
211 related = document.getElementById( this.getAttribute( 'data-for' ) );
212
213 SF.classList.toggle( this, 'collapsed' );
214
215 if ( SF.classList.contains( this, 'collapsed' ) ) {
216 SF.classList.add( related, 'collapsed' );
217 if ( containsIcon ) {
218 SF.classList.remove( this, 'icon-toggler-expanded' );
219 SF.classList.add( this, 'icon-toggler-collapsed' );
220 }
221 } else {
222 SF.classList.remove( related, 'collapsed' );
223 if ( containsIcon ) {
224 SF.classList.remove( this, 'icon-toggler-collapsed' );
225 SF.classList.add( this, 'icon-toggler-expanded' );
226 }
227 }
228 } );
229 }
230 } )();
231 /* global SF */
232
233 'use strict';
234
235 ( function() {
236 // All .tree-a elements in DOM.
237 var trees = SF.getByClass( 'tree-a' );
238
239 for ( var i = trees.length; i--; ) {
240 var tree = trees[ i ];
241
242 SF.attachListener( tree, 'click', function( evt ) {
243 var target = evt.target || evt.srcElement;
244
245 // Collapse or expand item groups.
246 if ( target.nodeName === 'H2' && !SF.classList.contains( target, 'tree-a-no-sub' ) ) {
247 SF.classList.toggle( target, 'tree-a-active' );
248 }
249 } );
250 }
251 } )();
252 // jshint ignore:start
253 // jscs:disable
254 /**
255 * Permission is hereby granted, free of charge, to any person obtaining a copy
256 * of this software and associated documentation files (the "Software"), to deal
257 * in the Software without restriction, including without limitation the rights
258 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
259 * copies of the Software, and to permit persons to whom the Software is
260 * furnished to do so, subject to the following conditions:
261 *
262 * The above copyright notice and this permission notice shall be included in
263 * all copies or substantial portions of the Software.
264 *
265 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
266 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
267 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
268 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
269 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
270 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
271 * SOFTWARE.
272 */
273
274 /**
275 * A self-contained modal library
276 */
277 (function(window, document) {
278 "use strict";
279
280 /** Returns whether a value is a dom node */
281 function isNode(value) {
282 if ( typeof Node === "object" ) {
283 return value instanceof Node;
284 }
285 else {
286 return value &&
287 typeof value === "object" &&
288 typeof value.nodeType === "number";
289 }
290 }
291
292 /** Returns whether a value is a string */
293 function isString(value) {
294 return typeof value === "string";
295 }
296
297 /**
298 * Generates observable objects that can be watched and triggered
299 */
300 function observable() {
301 var callbacks = [];
302 return {
303 watch: callbacks.push.bind(callbacks),
304 trigger: function( modal ) {
305
306 var unprevented = true;
307 var event = {
308 preventDefault: function preventDefault () {
309 unprevented = false;
310 }
311 };
312
313 for (var i = 0; i < callbacks.length; i++) {
314 callbacks[i](modal, event);
315 }
316
317 return unprevented;
318 }
319 };
320 }
321
322
323 /**
324 * A small interface for creating and managing a dom element
325 */
326 function Elem( elem ) {
327 this.elem = elem;
328 }
329
330 /**
331 * Creates a new div
332 */
333 Elem.div = function ( parent ) {
334 var elem = document.createElement('div');
335 (parent || document.body).appendChild(elem);
336 return new Elem(elem);
337 };
338
339 Elem.prototype = {
340
341 /** Creates a child of this node */
342 child: function () {
343 return Elem.div(this.elem);
344 },
345
346 /** Applies a set of styles to an element */
347 stylize: function(styles) {
348 styles = styles || {};
349
350 if ( typeof styles.opacity !== "undefined" ) {
351 styles.filter =
352 "alpha(opacity=" + (styles.opacity * 100) + ")";
353 }
354
355 for (var prop in styles) {
356 if (styles.hasOwnProperty(prop)) {
357 this.elem.style[prop] = styles[prop];
358 }
359 }
360
361 return this;
362 },
363
364 /** Adds a class name */
365 clazz: function (clazz) {
366 this.elem.className += " " + clazz;
367 return this;
368 },
369
370 /** Sets the HTML */
371 html: function (content) {
372 if ( isNode(content) ) {
373 this.elem.appendChild( content );
374 }
375 else {
376 this.elem.innerHTML = content;
377 }
378 return this;
379 },
380
381 /** Adds a click handler to this element */
382 onClick: function(callback) {
383 this.elem.addEventListener('click', callback);
384 return this;
385 },
386
387 /** Removes this element from the DOM */
388 destroy: function() {
389 document.body.removeChild(this.elem);
390 },
391
392 /** Hides this element */
393 hide: function() {
394 this.elem.style.display = "none";
395 },
396
397 /** Shows this element */
398 show: function() {
399 this.elem.style.display = "block";
400 },
401
402 /** Sets an attribute on this element */
403 attr: function ( name, value ) {
404 this.elem.setAttribute(name, value);
405 return this;
406 },
407
408 /** Executes a callback on all the ancestors of an element */
409 anyAncestor: function ( predicate ) {
410 var elem = this.elem;
411 while ( elem ) {
412 if ( predicate( new Elem(elem) ) ) {
413 return true;
414 }
415 else {
416 elem = elem.parentNode;
417 }
418 }
419 return false;
420 }
421 };
422
423
424 /** Generates the grey-out effect */
425 function buildOverlay( getOption, close ) {
426 return Elem.div()
427 .clazz("pico-overlay")
428 .clazz( getOption("overlayClass", "") )
429 .stylize({
430 display: "block",
431 position: "fixed",
432 top: "0px",
433 left: "0px",
434 height: "100%",
435 width: "100%",
436 zIndex: 10000
437 })
438 .stylize(getOption('overlayStyles', {
439 opacity: 0.5,
440 background: "#000"
441 }))
442 .onClick(function () {
443 if ( getOption('overlayClose', true) ) {
444 close();
445 }
446 });
447 }
448
449 /** Builds the content of a modal */
450 function buildModal( getOption, close ) {
451 var width = getOption('width', 'auto');
452 if ( typeof width === "number" ) {
453 width = "" + width + "px";
454 }
455
456 var elem = Elem.div()
457 .clazz("pico-content")
458 .clazz( getOption("modalClass", "") )
459 .stylize({
460 display: 'block',
461 position: 'fixed',
462 zIndex: 10001,
463 left: "50%",
464 top: "50px",
465 width: width,
466 '-ms-transform': 'translateX(-50%)',
467 '-moz-transform': 'translateX(-50%)',
468 '-webkit-transform': 'translateX(-50%)',
469 '-o-transform': 'translateX(-50%)',
470 'transform': 'translateX(-50%)'
471 })
472 .stylize(getOption('modalStyles', {
473 backgroundColor: "white",
474 padding: "20px",
475 borderRadius: "5px"
476 }))
477 .html( getOption('content') )
478 .attr("role", "dialog")
479 .onClick(function (event) {
480 var isCloseClick = new Elem(event.target)
481 .anyAncestor(function (elem) {
482 return /\bpico-close\b/.test(elem.elem.className);
483 });
484 if ( isCloseClick ) {
485 close();
486 }
487 });
488
489 return elem;
490 }
491
492 /** Builds the close button */
493 function buildClose ( elem, getOption ) {
494 if ( getOption('closeButton', true) ) {
495 return elem.child()
496 .html( getOption('closeHtml', "&#xD7;") )
497 .clazz("pico-close")
498 .clazz( getOption("closeClass") )
499 .stylize( getOption('closeStyles', {
500 borderRadius: "2px",
501 cursor: "pointer",
502 height: "15px",
503 width: "15px",
504 position: "absolute",
505 top: "5px",
506 right: "5px",
507 fontSize: "16px",
508 textAlign: "center",
509 lineHeight: "15px",
510 background: "#CCC"
511 }) );
512 }
513 }
514
515 /** Builds a method that calls a method and returns an element */
516 function buildElemAccessor( builder ) {
517 return function () {
518 return builder().elem;
519 };
520 }
521
522
523 /**
524 * Displays a modal
525 */
526 function picoModal(options) {
527
528 if ( isString(options) || isNode(options) ) {
529 options = { content: options };
530 }
531
532 var afterCreateEvent = observable();
533 var beforeShowEvent = observable();
534 var afterShowEvent = observable();
535 var beforeCloseEvent = observable();
536 var afterCloseEvent = observable();
537
538 /**
539 * Returns a named option if it has been explicitly defined. Otherwise,
540 * it returns the given default value
541 */
542 function getOption ( opt, defaultValue ) {
543 var value = options[opt];
544 if ( typeof value === "function" ) {
545 value = value( defaultValue );
546 }
547 return value === undefined ? defaultValue : value;
548 }
549
550 /** Hides this modal */
551 function forceClose () {
552 shadowElem().hide();
553 modalElem().hide();
554 afterCloseEvent.trigger(iface);
555 }
556
557 /** Gracefully hides this modal */
558 function close () {
559 if ( beforeCloseEvent.trigger(iface) ) {
560 forceClose();
561 }
562 }
563
564 /** Wraps a method so it returns the modal interface */
565 function returnIface ( callback ) {
566 return function () {
567 callback.apply(this, arguments);
568 return iface;
569 };
570 }
571
572
573 // The constructed dom nodes
574 var built;
575
576 /** Builds a method that calls a method and returns an element */
577 function build ( name ) {
578 if ( !built ) {
579 var modal = buildModal(getOption, close);
580 built = {
581 modal: modal,
582 overlay: buildOverlay(getOption, close),
583 close: buildClose(modal, getOption)
584 };
585 afterCreateEvent.trigger(iface);
586 }
587 return built[name];
588 }
589
590 var modalElem = build.bind(window, 'modal');
591 var shadowElem = build.bind(window, 'overlay');
592 var closeElem = build.bind(window, 'close');
593
594
595 var iface = {
596
597 /** Returns the wrapping modal element */
598 modalElem: buildElemAccessor(modalElem),
599
600 /** Returns the close button element */
601 closeElem: buildElemAccessor(closeElem),
602
603 /** Returns the overlay element */
604 overlayElem: buildElemAccessor(shadowElem),
605
606 /** Shows this modal */
607 show: function () {
608 if ( beforeShowEvent.trigger(iface) ) {
609 shadowElem().show();
610 closeElem();
611 modalElem().show();
612 afterShowEvent.trigger(iface);
613 }
614 return this;
615 },
616
617 /** Hides this modal */
618 close: returnIface(close),
619
620 /**
621 * Force closes this modal. This will not call beforeClose
622 * events and will just immediately hide the modal
623 */
624 forceClose: returnIface(forceClose),
625
626 /** Destroys this modal */
627 destroy: function () {
628 modalElem = modalElem().destroy();
629 shadowElem = shadowElem().destroy();
630 closeElem = undefined;
631 },
632
633 /**
634 * Updates the options for this modal. This will only let you
635 * change options that are re-evaluted regularly, such as
636 * `overlayClose`.
637 */
638 options: function ( opts ) {
639 options = opts;
640 },
641
642 /** Executes after the DOM nodes are created */
643 afterCreate: returnIface(afterCreateEvent.watch),
644
645 /** Executes a callback before this modal is closed */
646 beforeShow: returnIface(beforeShowEvent.watch),
647
648 /** Executes a callback after this modal is shown */
649 afterShow: returnIface(afterShowEvent.watch),
650
651 /** Executes a callback before this modal is closed */
652 beforeClose: returnIface(beforeCloseEvent.watch),
653
654 /** Executes a callback after this modal is closed */
655 afterClose: returnIface(afterCloseEvent.watch)
656 };
657
658 return iface;
659 }
660
661 if ( typeof window.define === "function" && window.define.amd ) {
662 window.define(function () {
663 return picoModal;
664 });
665 }
666 else {
667 window.picoModal = picoModal;
668 }
669
670 }(window, document));
671
672 // jscs:enable
673 // jshint ignore:end