I'm having an issue minifying one of my directives.
(function () {
angular.module("inflightApp.bnLazySrc", [])
.directive(
"bnLazySrc",
function( $window, $document) {
// I manage all the images that are currently being
// monitored on the page for lazy loading.
var lazyLoader = (function() {
// I maintain a list of images that lazy-loading
// and have yet to be rendered.
var images = [];
// I define the render timer for the lazy loading
// images to that the DOM-querying (for offsets)
// is chunked in groups.
var renderTimer = null;
var renderDelay = 100;
// I cache the window element as a jQuery reference.
var win = $( $window );
// I cache the document document height so that
// we can respond to changes in the height due to
// dynamic content.
var doc = $document;
var documentHeight = doc.height();
var documentTimer = null;
var documentDelay = 2000;
// I determine if the window dimension events
// (ie. resize, scroll) are currenlty being
// monitored for changes.
var isWatchingWindow = false;
// ---
// PUBLIC METHODS.
// ---
// I start monitoring the given image for visibility
// and then render it when necessary.
function addImage( image ) {
images.push( image );
if ( ! renderTimer ) {
startRenderTimer();
}
if ( ! isWatchingWindow ) {
startWatchingWindow();
}
}
// I remove the given image from the render queue.
function removeImage( image ) {
// Remove the given image from the render queue.
for ( var i = 0 ; i < images.length ; i++ ) {
if ( images[ i ] === image ) {
images.splice( i, 1 );
break;
}
}
// If removing the given image has cleared the
// render queue, then we can stop monitoring
// the window and the image queue.
if ( ! images.length ) {
clearRenderTimer();
stopWatchingWindow();
}
}
// ---
// PRIVATE METHODS.
// ---
// I check the document height to see if it's changed.
function checkDocumentHeight() {
// If the render time is currently active, then
// don't bother getting the document height -
// it won't actually do anything.
if ( renderTimer ) {
return;
}
var currentDocumentHeight = doc.height();
// If the height has not changed, then ignore -
// no more images could have come into view.
if ( currentDocumentHeight === documentHeight ) {
return;
}
// Cache the new document height.
documentHeight = currentDocumentHeight;
startRenderTimer();
}
// I check the lazy-load images that have yet to
// be rendered.
function checkImages() {
// Log here so we can see how often this
// gets called during page activity.
console.log( "Checking for visible images..." );
var visible = [];
var hidden = [];
// Determine the window dimensions.
var windowHeight = win.height();
var scrollTop = win.scrollTop();
// Calculate the viewport offsets.
var topFoldOffset = scrollTop;
var bottomFoldOffset = ( topFoldOffset + windowHeight );
// Query the DOM for layout and seperate the
// images into two different categories: those
// that are now in the viewport and those that
// still remain hidden.
for ( var i = 0 ; i < images.length ; i++ ) {
var image = images[ i ];
if ( image.isVisible( topFoldOffset, bottomFoldOffset ) ) {
visible.push( image );
} else {
hidden.push( image );
}
}
// Update the DOM with new image source values.
for ( var i = 0 ; i < visible.length ; i++ ) {
visible[ i ].render();
}
// Keep the still-hidden images as the new
// image queue to be monitored.
images = hidden;
// Clear the render timer so that it can be set
// again in response to window changes.
clearRenderTimer();
// If we've rendered all the images, then stop
// monitoring the window for changes.
if ( ! images.length ) {
stopWatchingWindow();
}
}
// I clear the render timer so that we can easily
// check to see if the timer is running.
function clearRenderTimer() {
clearTimeout( renderTimer );
renderTimer = null;
}
// I start the render time, allowing more images to
// be added to the images queue before the render
// action is executed.
function startRenderTimer() {
renderTimer = setTimeout( checkImages, renderDelay );
}
// I start watching the window for changes in dimension.
function startWatchingWindow() {
isWatchingWindow = true;
// Listen for window changes.
win.on( "resize.bnLazySrc", windowChanged );
win.on( "scroll.bnLazySrc", windowChanged );
// Set up a timer to watch for document-height changes.
documentTimer = setInterval( checkDocumentHeight, documentDelay );
}
// I stop watching the window for changes in dimension.
function stopWatchingWindow() {
isWatchingWindow = false;
// Stop watching for window changes.
win.off( "resize.bnLazySrc" );
win.off( "scroll.bnLazySrc" );
// Stop watching for document changes.
clearInterval( documentTimer );
}
// I start the render time if the window changes.
function windowChanged() {
if ( ! renderTimer ) {
startRenderTimer();
}
}
// Return the public API.
return({
addImage: addImage,
removeImage: removeImage
});
})();
// ------------------------------------------ //
// ------------------------------------------ //
// I represent a single lazy-load image.
function LazyImage( element ) {
// I am the interpolated LAZY SRC attribute of
// the image as reported by AngularJS.
var source = null;
// I determine if the image has already been
// rendered (ie, that it has been exposed to the
// viewport and the source had been loaded).
var isRendered = false;
// I am the cached height of the element. We are
// going to assume that the image doesn't change
// height over time.
var height = null;
// ---
// PUBLIC METHODS.
// ---
// I determine if the element is above the given
// fold of the page.
function isVisible( topFoldOffset, bottomFoldOffset ) {
// If the element is not visible because it
// is hidden, don't bother testing it.
if ( ! element.is( ":visible" ) ) {
return( false );
}
// If the height has not yet been calculated,
// the cache it for the duration of the page.
if ( height === null ) {
height = element.height();
}
// Update the dimensions of the element.
var top = element.offset().top;
var bottom = ( top + height );
// Return true if the element is:
// 1. The top offset is in view.
// 2. The bottom offset is in view.
// 3. The element is overlapping the viewport.
return(
(
( top <= bottomFoldOffset ) &&
( top >= topFoldOffset )
)
||
(
( bottom <= bottomFoldOffset ) &&
( bottom >= topFoldOffset )
)
||
(
( top <= topFoldOffset ) &&
( bottom >= bottomFoldOffset )
)
);
}
// I move the cached source into the live source.
function render() {
isRendered = true;
renderSource();
}
// I set the interpolated source value reported
// by the directive / AngularJS.
function setSource( newSource ) {
source = newSource;
if ( isRendered ) {
renderSource();
}
}
// ---
// PRIVATE METHODS.
// ---
// I load the lazy source value into the actual
// source value of the image element.
function renderSource() {
element[ 0 ].src = source;
}
// Return the public API.
return({
isVisible: isVisible,
render: render,
setSource: setSource
});
}
// ------------------------------------------ //
// ------------------------------------------ //
// I bind the UI events to the scope.
function link( $scope, element, attributes ) {
var lazyImage = new LazyImage( element );
// Start watching the image for changes in its
// visibility.
lazyLoader.addImage( lazyImage );
// Since the lazy-src will likely need some sort
// of string interpolation, we don't want to
attributes.$observe(
"bnLazySrc",
function( newSource ) {
lazyImage.setSource( newSource );
}
);
// When the scope is destroyed, we need to remove
// the image from the render queue.
$scope.$on(
"$destroy",
function() {
lazyLoader.removeImage( lazyImage );
}
);
}
// Return the directive configuration.
return({
link: link,
restrict: "A"
});
});
}());
Because you need to annotate your functions with the names of the dependencies.
See the documentation.
Related
I am building an application on react where I have to show a 3D model (glTF) with an animation.
I am using "three-gltf-loader" npm package and I am able to load the 3D model correctly. But while playing the animation it's not working fine. As soon as I add the code to initiate the animation the model is becoming distorted.
I tried searching the solution over google, but its not working out for me. I am using the basic code base from this URL "https://medium.com/#colesayershapiro/using-three-js-in-react-6cb71e87bdf4".
The following code is on the function "componentDidMount()"
let clock = new THREE.Clock();
const loader = new GLTFLoader();
loader.load(
objMap,
( gltf ) => {
const model = gltf.scene;
// called when the resource is loaded
model.traverse( function ( child ) {
if ( child.isMesh ) {
child.geometry.center(); // center here
}
});
model.scale.set(100,100,100);
if (gltf.animations && gltf.animations.length) {
const mixer = new THREE.AnimationMixer(model);
mixers.push( mixer );
for (var i = 0; i < gltf.animations.length; i++) {
var animation = gltf.animations[3];
if (animation.name == 'Idle'){
mixer.clipAction(animation).play();
}
}
}
scene.add( model );
},
( xhr ) => {
// called while loading is progressing
console.log( `${( xhr.loaded / xhr.total * 100 )}% loaded` );
},
( error ) => {
// called when loading has errors
console.error( 'An error happened', error );
},
);
The following code is on the function "animate()"
for ( let mixer of this.mixers ) {
mixer.update( delta );
}
...
I;'m working in a project that's uses a custom browser. This custom browser injects an object inside the window object. I need to track the changes...something like...
componentDidMount() {
window.customObject.addListener('onchange' , (changes) => console.log(changes))
}
The example above is just hypothetical to make this thing more easy to understand. Is there some how to archive it ?
Yes, you can observe dom changes with MutationObserver api.
var observeDOM = (function(){
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
return function( obj, callback ){
if( !obj || !obj.nodeType === 1 ) return; // validation
if( MutationObserver ){
// define a new observer
var obs = new MutationObserver(function(mutations, observer){
if( mutations[0].addedNodes.length || mutations[0].removedNodes.length )
callback( mutations[0] );
});
// have the observer observe foo for changes in children
obs.observe( obj, { childList:true, subtree:true });
}
else if( window.addEventListener ){
obj.addEventListener('DOMNodeInserted', callback, false);
obj.addEventListener('DOMNodeRemoved', callback, false);
}
}
})();
Usage example:
// Observe a specific DOM element:
observeDOM(document.querySelector('body'), function(m) {
console.log('Added:', m.addedNodes, 'Removed:', m.removedNodes);
});
I am working on an application which is using ammaps. I have a number of points located on the map based on longitude and latitude value. I have achieved single click functionality by using the following code:
map.addListener("clickMapObject", function (event) {
$scope.$apply(function(){
$scope.colorPoints();
$scope.selectedRow = event.mapObject.idBase;
});
});
I want to achieve the functionality of double click. Could anyone let me know how I could do that in amMaps.
Technically, amMap does not support double-click events. However, you can simulate it with a clickMapObject event.
For that you'll need to ignore the first click. If the subsequent clickMapObject happens within 500ms or so, you register it as double-click.
Something like this:
map.addListener( "clickMapObject", function( event ) {
if ( false !== map.clickedObject && map.clickedObject === event.mapObject ) {
// doubleckick
map.clickedObject = false;
$scope.$apply( function() {
$scope.colorPoints();
$scope.selectedRow = event.mapObject.idBase;
} );
} else {
clearTimeout( map.clickedObjectTimeout );
map.clickedObject = event.mapObject;
map.clickedObjectTimeout = setTimeout( function() {
map.clickedObject = false;
}, 500 );
}
} );
When I copy text from a google doc and paste it to the ckeditor using the paste from word button it removes all the styling (bold, italics, ...).
How can I fix that?
Steps to reproduce :
Copy one word from a google doc that is both underlined and italicized Paste it into editor.
Expected result :
The word is pasted in italicized and underlined.
Actual result:
The word is bolded, no underlines or italics to be found.
Add the following lines in config.js:
config.pasteFromWordRemoveFontStyles = false;
config.pasteFromWordRemoveStyles = false;
Then use the "Paste from Word" button (marked below), don't paste directly using CTRL+V or CMD+V.
I'm posting my solution in case it helps someone.
I had the same issue with pasting from googleDocs.
And I found someone made a branch of CKEditor in github (13877) which corrects this.
As I needed it to work with my version 4.5.6, I then adapted it into a plugin.
I have done this to work with Processwire which I cannont change the CKEditor version, and have only tested it in this context.
pasteFromGoogleDoc (folder)
plugin.js
content of plugin.js :
/**
* #license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or http://ckeditor.com/license
* 2017-07-05
* the code originally from Frederico Knabben, written in ckeditor-dev-t-13877 branch
* (https://github.com/cksource/ckeditor-dev/tree/t/13877)
* has been adapted in a plugin by Rpapier for use in Processwire. It hasn't been tested elsewhere.
* DESCRIPTION
* Filter to paste from Google Doc and keep style (bold, italic, underline)
* Those style must be present in the toolbar for it to show, otherwise, it will bypass it
* For processwire :
* you must edit the field that has CKEditor and make sure that :
* - ACF is On
* - pasteFromGoogleDoc plugin is enabled
* - CKEditor toolbar configuration contains Bold, Italic and Underline
* - e.g : Format, Styles, -, Bold, Italic, Underline, -, RemoveFormat
* if Underline is not in the toolbar for example, it will be bypassed by the filter.
*/
( function() {
'use strict';
CKEDITOR.plugins.add( 'pasteFromGoogleDoc', {
requires: ['clipboard'],
init: function( editor ) {
// === arteractive hack for pasteFromGoogleDoc
var filterType,
filtersFactory = filtersFactoryFactory();
if ( editor.config.forcePasteAsPlainText ) {
filterType = 'plain-text';
} else if ( editor.config.pasteFilter ) {
filterType = editor.config.pasteFilter;
}
// On Webkit the pasteFilter defaults to 'webkit-default-filter' because pasted data is so terrible
// that it must be always filtered. (#13877)
else if ( CKEDITOR.env.webkit && !( 'pasteFilter' in editor.config ) ) {
filterType = 'webkit-default-filter';
}
//console.log(filterType);
editor.pasteFilter = filtersFactory.get( filterType );
editor.on( 'paste', function( evt ) {
// Init `dataTransfer` if `paste` event was fired without it, so it will be always available.
if ( !evt.data.dataTransfer ) {
evt.data.dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer();
}
// If dataValue is already set (manually or by paste bin), so do not override it.
if ( evt.data.dataValue ) {
return;
}
var dataTransfer = evt.data.dataTransfer,
// IE support only text data and throws exception if we try to get html data.
// This html data object may also be empty if we drag content of the textarea.
value = dataTransfer.getData( 'text/html' );
if ( value ) {
evt.data.dataValue = value;
evt.data.type = 'html';
} else {
// Try to get text data otherwise.
value = dataTransfer.getData( 'text/plain' );
if ( value ) {
evt.data.dataValue = editor.editable().transformPlainTextToHtml( value );
evt.data.type = 'text';
}
}
}, null, null, 1 );
editor.on( 'paste', function( evt ) {
var dataObj = evt.data,
type = dataObj.type,
data = dataObj.dataValue,
trueType,
// Default is 'html'.
defaultType = editor.config.clipboard_defaultContentType || 'html',
transferType = dataObj.dataTransfer.getTransferType( editor );
// If forced type is 'html' we don't need to know true data type.
if ( type == 'html' || dataObj.preSniffing == 'html' ) {
trueType = 'html';
} else {
trueType = recogniseContentType( data );
}
// Unify text markup.
if ( trueType == 'htmlifiedtext' ) {
data = htmlifiedTextHtmlification( editor.config, data );
}
// Strip presentational markup & unify text markup.
// Forced plain text (dialog or forcePAPT).
// Note: we do not check dontFilter option in this case, because forcePAPT was implemented
// before pasteFilter and pasteFilter is automatically used on Webkit&Blink since 4.5, so
// forcePAPT should have priority as it had before 4.5.
if ( type == 'text' && trueType == 'html' ) {
data = filterContent( editor, data, filtersFactory.get( 'plain-text' ) );
}
// External paste and pasteFilter exists and filtering isn't disabled.
else if ( transferType == CKEDITOR.DATA_TRANSFER_EXTERNAL && editor.pasteFilter && !dataObj.dontFilter ) {
// 2017-07-05 comment out this filter because it is already processsed somewhere...
//data = filterContent( editor, data, editor.pasteFilter );
//console.log(data);
}
if ( dataObj.startsWithEOL ) {
data = '<br data-cke-eol="1">' + data;
}
if ( dataObj.endsWithEOL ) {
data += '<br data-cke-eol="1">';
}
if ( type == 'auto' ) {
type = ( trueType == 'html' || defaultType == 'html' ) ? 'html' : 'text';
}
dataObj.type = type;
dataObj.dataValue = data;
delete dataObj.preSniffing;
delete dataObj.startsWithEOL;
delete dataObj.endsWithEOL;
/* evt.data.dataValue = data;
evt.data.dataValue = evt.data.dataValue
.replace( /zooterkins/gi, 'z********s' )
.replace( /gadzooks/gi, 'g******s' );*/
}, null, null, 6 );
}
} );
function filterContent( editor, data, filter ) {
var fragment = CKEDITOR.htmlParser.fragment.fromHtml( data ),
writer = new CKEDITOR.htmlParser.basicWriter();
filter.applyTo( fragment, true, false, editor.activeEnterMode );
fragment.writeHtml( writer );
return writer.getHtml();
}
// Returns:
// * 'htmlifiedtext' if content looks like transformed by browser from plain text.
// See clipboard/paste.html TCs for more info.
// * 'html' if it is not 'htmlifiedtext'.
function recogniseContentType( data ) {
if ( CKEDITOR.env.webkit ) {
// Plain text or ( <div><br></div> and text inside <div> ).
if ( !data.match( /^[^<]*$/g ) && !data.match( /^(<div><br( ?\/)?><\/div>|<div>[^<]*<\/div>)*$/gi ) )
return 'html';
} else if ( CKEDITOR.env.ie ) {
// Text and <br> or ( text and <br> in <p> - paragraphs can be separated by new \r\n ).
if ( !data.match( /^([^<]|<br( ?\/)?>)*$/gi ) && !data.match( /^(<p>([^<]|<br( ?\/)?>)*<\/p>|(\r\n))*$/gi ) )
return 'html';
} else if ( CKEDITOR.env.gecko ) {
// Text or <br>.
if ( !data.match( /^([^<]|<br( ?\/)?>)*$/gi ) )
return 'html';
} else {
return 'html';
}
return 'htmlifiedtext';
}
// This function transforms what browsers produce when
// pasting plain text into editable element (see clipboard/paste.html TCs
// for more info) into correct HTML (similar to that produced by text2Html).
function htmlifiedTextHtmlification( config, data ) {
function repeatParagraphs( repeats ) {
// Repeat blocks floor((n+1)/2) times.
// Even number of repeats - add <br> at the beginning of last <p>.
return CKEDITOR.tools.repeat( '</p><p>', ~~( repeats / 2 ) ) + ( repeats % 2 == 1 ? '<br>' : '' );
}
// Replace adjacent white-spaces (EOLs too - Fx sometimes keeps them) with one space.
data = data.replace( /\s+/g, ' ' )
// Remove spaces from between tags.
.replace( /> +</g, '><' )
// Normalize XHTML syntax and upper cased <br> tags.
.replace( /<br ?\/>/gi, '<br>' );
// IE - lower cased tags.
data = data.replace( /<\/?[A-Z]+>/g, function( match ) {
return match.toLowerCase();
} );
// Don't touch single lines (no <br|p|div>) - nothing to do here.
if ( data.match( /^[^<]$/ ) )
return data;
// Webkit.
if ( CKEDITOR.env.webkit && data.indexOf( '<div>' ) > -1 ) {
// One line break at the beginning - insert <br>
data = data.replace( /^(<div>(<br>|)<\/div>)(?!$|(<div>(<br>|)<\/div>))/g, '<br>' )
// Two or more - reduce number of new lines by one.
.replace( /^(<div>(<br>|)<\/div>){2}(?!$)/g, '<div></div>' );
// Two line breaks create one paragraph in Webkit.
if ( data.match( /<div>(<br>|)<\/div>/ ) ) {
data = '<p>' + data.replace( /(<div>(<br>|)<\/div>)+/g, function( match ) {
return repeatParagraphs( match.split( '</div><div>' ).length + 1 );
} ) + '</p>';
}
// One line break create br.
data = data.replace( /<\/div><div>/g, '<br>' );
// Remove remaining divs.
data = data.replace( /<\/?div>/g, '' );
}
// Opera and Firefox and enterMode != BR.
if ( CKEDITOR.env.gecko && config.enterMode != CKEDITOR.ENTER_BR ) {
// Remove bogus <br> - Fx generates two <brs> for one line break.
// For two line breaks it still produces two <brs>, but it's better to ignore this case than the first one.
if ( CKEDITOR.env.gecko )
data = data.replace( /^<br><br>$/, '<br>' );
// This line satisfy edge case when for Opera we have two line breaks
//data = data.replace( /)
if ( data.indexOf( '<br><br>' ) > -1 ) {
// Two line breaks create one paragraph, three - 2, four - 3, etc.
data = '<p>' + data.replace( /(<br>){2,}/g, function( match ) {
return repeatParagraphs( match.length / 4 );
} ) + '</p>';
}
}
return switchEnterMode( config, data );
}
function filtersFactoryFactory() {
var filters = {};
// GDocs generates many spans and divs, therefore `all` parameter is used
// to create default filter in Webkit/Blink. (#13877)
function setUpTags( all ) {
var tags = {};
for ( var tag in CKEDITOR.dtd ) {
if ( tag.charAt( 0 ) != '$' && ( all || tag != 'div' && tag != 'span') ) {
tags[ tag ] = 1;
}
}
return tags;
}
// Checks if content is pasted from Google Docs.
// Google Docs wraps everything in element with [id^=docs-internal-guid-],
// so that function just checks if such element exists. (#13877)
function isPastedFromGDocs( element ) {
if ( element.attributes.id && element.attributes.id.match( /^docs\-internal\-guid\-/ ) ) {
return true;
} else if ( element.parent && element.parent.name ) {
return isPastedFromGDocs( element.parent );
}
return false;
}
// Process data from Google Docs:
// * turns `*[id^=docs-internal-guid-]` into `span`;
// * turns `span(text-decoration=underline)` into `u`;
// * turns `span(font-style=italic)` into `em`
// * turns `span(font-style=italic)(text-decoration=underline)` into `u > em`. (#13877)
//
function processDataFromGDocs( element ) {
var styles = element.attributes.style && CKEDITOR.tools.parseCssText( element.attributes.style );
if ( element.attributes.id && element.attributes.id.match( /^docs\-internal\-guid\-/ ) ) {
return element.name = 'span';
}
if ( !styles ) {
return;
}
if ( styles[ 'font-style' ] == 'italic' && styles[ 'text-decoration' ] == 'underline' ) {
element.name = 'em';
element.wrapWith( new CKEDITOR.htmlParser.element( 'u' ) );
if (styles[ 'font-weight' ] > 400) {
element.wrapWith( new CKEDITOR.htmlParser.element( 'strong' ) );
}
} else if ( styles[ 'text-decoration' ] == 'underline' ) {
element.name = 'u';
if (styles[ 'font-weight' ] > 400) {
element.wrapWith( new CKEDITOR.htmlParser.element( 'strong' ) );
}
} else if ( styles[ 'font-style' ] == 'italic' ) {
element.name = 'em';
if (styles[ 'font-weight' ] > 400) {
element.wrapWith( new CKEDITOR.htmlParser.element( 'strong' ) );
}
}
}
function createSemanticContentFilter() {
var filter = new CKEDITOR.filter();
filter.allow( {
$1: {
elements: setUpTags(),
attributes: true,
styles: false,
classes: false
}
} );
return filter;
}
function createWebkitDefaultFilter() {
var filter = createSemanticContentFilter();
// Preserves formatting while pasting from Google Docs in Webkit/Blink
// with default paste filter. (#13877)
filter.allow( {
$2: {
elements: setUpTags( true ),
attributes: true,
styles: true,
match: function( element ) {
return isPastedFromGDocs( element );
}
}
} );
filter.addElementCallback( processDataFromGDocs );
return filter;
}
return {
get: function( type ) {
if ( type == 'plain-text' ) {
// Does this look confusing to you? Did we forget about enter mode?
// It is a trick that let's us creating one filter for edidtor, regardless of its
// activeEnterMode (which as the name indicates can change during runtime).
//
// How does it work?
// The active enter mode is passed to the filter.applyTo method.
// The filter first marks all elements except <br> as disallowed and then tries to remove
// them. However, it cannot remove e.g. a <p> element completely, because it's a basic structural element,
// so it tries to replace it with an element created based on the active enter mode, eventually doing nothing.
//
// Now you can sleep well.
return filters.plainText || ( filters.plainText = new CKEDITOR.filter( 'br' ) );
} else if ( type == 'semantic-content' ) {
return filters.semanticContent || ( filters.semanticContent = createSemanticContentFilter() );
} else if ( type == 'webkit-default-filter' ) {
// Webkit based browsers need semantic filter, because they produce terrible HTML without it.
// However original `'semantic-content'` filer is too strict and prevents pasting styled contents
// from many sources (e.g. Google Docs). Therefore that type extends original `'semantic-content'` filter. (#13877)
return filters.webkitDefaultFilter || ( filters.webkitDefaultFilter = createWebkitDefaultFilter() );
} else if ( type ) {
// Create filter based on rules (string or object).
return new CKEDITOR.filter( type );
}
return null;
}
};
}
function switchEnterMode( config, data ) {
if ( config.enterMode == CKEDITOR.ENTER_BR ) {
data = data.replace( /(<\/p><p>)+/g, function( match ) {
return CKEDITOR.tools.repeat( '<br>', match.length / 7 * 2 );
} ).replace( /<\/?p>/g, '' );
} else if ( config.enterMode == CKEDITOR.ENTER_DIV ) {
data = data.replace( /<(\/)?p>/g, '<$1div>' );
}
return data;
}
} )();
Hopefully this is an easy answer.
I have this directive that controls the background images on the site. It rotates between images, but before it starts setting the background images, it actually preloads them. This is my code for the directive:
// ---
// CONTROLLERS.
// ---
.controller('BackgroundSwitcherController', ['$rootScope', 'Options', 'BackgroundSwitcherService', function ($rootScope, options, service) {
var self = this;
// Declare our variables
self.model = {
staticImage: '',
loading: false,
rotate: false
};
// On state change
$rootScope.$on('$stateChangeStart', function (event, toState) {
// Get our data
var data = toState.data;
// Set our rotate flag
self.model.rotate = options.current.rotateBackground;
// If we are set to rotate
if (self.model.rotate) {
// Make a call to our service (this is called every state change, but only preloaded once)
service.get().then(function (response) {
// Return the images in the response
self.model.images = response;
});
} else {
// Otherwise, reset the images
self.model.images = null;
}
// If we have data
if (data) {
// Reset our variables
self.model.staticImage = data.backgroundImage;
//self.model.loading = true;
}
});
}])
// ---
// SERVICES.
// ---
.service('BackgroundSwitcherService', ['$q', 'PreloaderService', function ($q, preloader) {
this.loading = true;
this.successful = false;
this.percentLoaded = 0;
// Get our images
this.get = function () {
// Our images
var images = [
'assets/backgrounds/Avebury Steps.jpg',
'assets/backgrounds/Avebury Stripe.jpg',
'assets/backgrounds/Avebury.jpg',
'assets/backgrounds/Forest Hills.jpg',
'assets/backgrounds/Inteface.jpg',
'assets/backgrounds/New Oaklands.jpg',
'assets/backgrounds/Primo Delight.jpg',
'assets/backgrounds/Secure 3d.jpg',
'assets/backgrounds/Secure.jpg',
'assets/backgrounds/Sensation Heathers.jpg',
'assets/backgrounds/Sensation.jpg',
'assets/backgrounds/Woman.jpg'
];
// Create our promise
var deferred = $q.defer();
// Preload the images
preloader.preloadImages(images).then(
// Function to handle the changing of the flags when all images have loaded
function handleResolve(imageLocations) {
this.loading = false;
this.successful = true;
deferred.resolve(images);
},
// Function to handle any errors
function handleReject(imageLocation) {
this.loading = false;
this.successful = false;
deferred.reject();
},
// Function that notifies our percentage loaded flag
function handleNotify(event) {
this.percentLoaded = event.percent;
}
);
// Return our promise
return deferred.promise;
};
}])
// ---
// DIRECTIVES.
// ---
.directive('backgroundSwitcher', ['$interval', function ($interval) {
// Variables
var target = null,
timer = null,
images = null,
currentIndex = 0;
// Function to get a random value between two numbers
function getRandomInt(min, max) {
// Return our random number
return Math.floor(Math.random() * (max - min)) + min;
};
var getImageIndex = function (length) {
// Get our random index
var index = getRandomInt(0, images.length);
// If our index matches the current index
if (index == currentIndex) {
// Run again until we get a different index
return getImageIndex(length);
}
// Set our current index our the new index
currentIndex = index;
// Return our index
return index;
}
// Apply the image
var applyImage = function () {
// Get a random image
var image = images[getImageIndex(images.length)];
// Apply our image to our target
applyStaticImage(image);
};
// Apply a static image
var applyStaticImage = function (image) {
// Apply our image to our target
target.css('background-image', 'url(' + escape(image) + ')');
};
// Remove any images
var removeImage = function () {
// Remove our background image
target.css('background-image', 'none');
};
// Start timer function
var startTimer = function () {
// If our timer is not running
if (!timer || timer.$$state.status === 2) {
// Apply our first image
applyImage();
// Start our timer
timer = $interval(changeImage, 10000);
}
};
// Function to change the background image
var changeImage = function () {
// Apply our image
applyImage();
};
// Stop the timer
var stopTimer = function () {
// If we have started our timer
if (timer) {
// Stop it
$interval.cancel(timer);
}
};
return {
restrict: 'A',
controller: 'BackgroundSwitcherController',
link: function (scope, element, attr, controller) {
// Assign our element to our global variable
target = element;
// Watch our image
scope.$watch(function () {
// Return our image
return controller.model.staticImage;
}, function (staticImage) {
// If we have an image
if (staticImage) {
// Stop our timer
stopTimer();
// Apply our static image
applyStaticImage(staticImage);
} else {
// If we are not rotating
if (!controller.model.rotate) {
// Remove any images
removeImage();
}
}
})
// Watch our rotate
scope.$watch(function () {
// Return our rotate flag
return controller.model.images;
}, function (array) {
// Set our variable
images = array;
// If we have some images
if (images) {
// If we don't have a static image
if (!controller.model.staticImage) {
// Start rotating our images
startTimer();
}
} else {
// Remove any images
removeImage();
// Otherwise, stop our timer
stopTimer();
}
});
// Destroy function to cancel all timers
element.on('$destroy', function () {
// Stop our timer
stopTimer();
});
}
}
}]);
and for the actual preloader I have this:
// ---
// SERVICES.
// ---
.factory('PreloaderService', function ($q, $rootScope) {
function Preloader(imageLocations) {
this.imageLocations = imageLocations;
this.imageCount = imageLocations.length;
this.loadCount = 0;
this.errorCount = 0;
this.states = {
PENDING: 1,
LOADING: 2,
RESOLVED: 3,
REJECTED: 4
};
this.state = this.states.PENDING;
// When loading the images, a promise will be returned to indicate
// when the loading has completed (and / or progressed).
this.deferred = $q.defer();
this.promise = this.deferred.promise;
}
// I reload the given images [Array] and return a promise. The promise
// will be resolved with the array of image locations.
Preloader.preloadImages = function (imageLocations) {
var preloader = new Preloader(imageLocations);
return (preloader.load());
};
Preloader.prototype = {
// Best practice for 'instnceof' operator.
constructor: Preloader,
// ---
// PUBLIC METHODS.
// ---
// I determine if the preloader has started loading images yet.
isInitiated: function isInitiated() {
return (this.state !== this.states.PENDING);
},
// I determine if the preloader has failed to load all of the images.
isRejected: function isRejected() {
return (this.state === this.states.REJECTED);
},
// I determine if the preloader has successfully loaded all of the images.
isResolved: function isResolved() {
return (this.state === this.states.RESOLVED);
},
// I initiate the preload of the images. Returns a promise.
load: function load() {
// If the images are already loading, return the existing promise.
if (this.isInitiated()) {
return (this.promise);
}
this.state = this.states.LOADING;
for (var i = 0 ; i < this.imageCount ; i++) {
this.loadImageLocation(this.imageLocations[i]);
}
// Return the deferred promise for the load event.
return (this.promise);
},
// ---
// PRIVATE METHODS.
// ---
// I handle the load-failure of the given image location.
handleImageError: function handleImageError(imageLocation) {
this.errorCount++;
// If the preload action has already failed, ignore further action.
if (this.isRejected()) {
return;
}
this.state = this.states.REJECTED;
this.deferred.reject(imageLocation);
},
// I handle the load-success of the given image location.
handleImageLoad: function handleImageLoad(imageLocation) {
this.loadCount++;
// If the preload action has already failed, ignore further action.
if (this.isRejected()) {
return;
}
// Notify the progress of the overall deferred. This is different
// than Resolving the deferred - you can call notify many times
// before the ultimate resolution (or rejection) of the deferred.
this.deferred.notify({
percent: Math.ceil(this.loadCount / this.imageCount * 100),
imageLocation: imageLocation
});
// If all of the images have loaded, we can resolve the deferred
// value that we returned to the calling context.
if (this.loadCount === this.imageCount) {
this.state = this.states.RESOLVED;
this.deferred.resolve(this.imageLocations);
}
},
// I load the given image location and then wire the load / error
// events back into the preloader instance.
// --
// NOTE: The load/error events trigger a $digest.
loadImageLocation: function loadImageLocation(imageLocation) {
var preloader = this;
// When it comes to creating the image object, it is critical that
// we bind the event handlers BEFORE we actually set the image
// source. Failure to do so will prevent the events from proper
// triggering in some browsers.
var image = angular.element(new Image());
image.bind('load', function (event) {
// Since the load event is asynchronous, we have to
// tell AngularJS that something changed.
$rootScope.$apply(
function () {
preloader.handleImageLoad(event.target.src);
// Clean up object reference to help with the
// garbage collection in the closure.
preloader = image = event = null;
}
);
}).bind('error', function (event) {
// Since the load event is asynchronous, we have to
// tell AngularJS that something changed.
$rootScope.$apply(
function () {
preloader.handleImageError(event.target.src);
// Clean up object reference to help with the
// garbage collection in the closure.
preloader = image = event = null;
}
);
}).prop('src', imageLocation);
}
};
// Return the factory instance.
return (Preloader);
});
Now, when I use this directive all my other images / font awesome icons pause before they load. It causes an issue because the font awesome icons actually briefly show a rectangular box before fully loading and the profile image is blank until loaded.
If I remove my directive from my HTML everything loads fine.
Now I realise that this is happening because it has to load an array of images for the background switcher, so what I would like to do is to not start the preloading until everything else has loaded first.
Can someone tell me if there is a simple way to do this?