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;
}
} )();
Related
Hello I have this code:
<script>
function storageAvailable(type) {
try {
var storage = window[type],
x = '__storage_test__';
storage.setItem(x, x);
storage.removeItem(x);
return true;
}
catch(e) {
return e instanceof DOMException && (
// everything except Firefox
e.code === 22 ||
// Firefox
e.code === 1014 ||
// test name field too, because code might not be present
// everything except Firefox
e.name === 'QuotaExceededError' ||
// Firefox
e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
// acknowledge QuotaExceededError only if there's something already stored
storage.length !== 0;
}
}
jQuery(document).ready(function($) {
var storageAvailable = window.storageAvailable('sessionStorage');
$(".et-dark-toggle").click(function() {
$(".et-dark-mode-capable,body").toggleClass("et-dark-mode");
if ( storageAvailable ) {
$("body").hasClass("et-dark-mode") ?
sessionStorage.setItem('etDarkModeEnabled','1'):
sessionStorage.removeItem('etDarkModeEnabled');
}
});
if (storageAvailable) {
'1' == sessionStorage.getItem('etDarkModeEnabled') ?
$(".et-dark-mode-capable,body").addClass("et-dark-mode"):
$(".et-dark-mode-capable,body").removeClass("et-dark-mode");
}
});
</script>
How can i add prefers-color-scheme media query to autoenable the created dark mode when user has dark mode enabled in browser ?
Can you please help me
Sorry for the late response. A quick code to integrate (p.s. not tested):
jQuery(document).ready(function($) {
var storageAvailable = window.storageAvailable('sessionStorage');
if(window.matchMedia)
{
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event =>
{
if (event.matches && !$("body").hasClass("et-dark-mode"))
{
$(".et-dark-mode-capable,body").addClass("et-dark-mode");
if ( storageAvailable )
{
sessionStorage.setItem('etDarkModeEnabled','1'):
}
}
else
{
$(".et-dark-mode-capable,body").removeClass("et-dark-mode");
if ( storageAvailable )
{
sessionStorage.removeItem('etDarkModeEnabled');
}
}
}
);
}
$(".et-dark-toggle").click(function() {
$(".et-dark-mode-capable,body").toggleClass("et-dark-mode");
if ( storageAvailable ) {
$("body").hasClass("et-dark-mode") ?
sessionStorage.setItem('etDarkModeEnabled','1'):
sessionStorage.removeItem('etDarkModeEnabled');
}
});
if (storageAvailable) {
'1' == sessionStorage.getItem('etDarkModeEnabled') ?
$(".et-dark-mode-capable,body").addClass("et-dark-mode"):
$(".et-dark-mode-capable,body").removeClass("et-dark-mode");
}
});
In short it checks if the browser supports window.matchMedia and then it reads it and sets the theme based on that preference. In the end it is the same as your code to allow the user to toggle the dark and light mode (maybe a button, or a checkbox).
For more info you can search some dev blogs on the subject and after a quick search I would say that this one might closely match your needs (p.s. not my blog :) )
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.
this code sets or removes class of the div when the input field is valid/invalid.
$( 'form' ).parsley(
'addListener', {
onFieldSuccess: function ( elem ) {
elem.parent().removeClass('err');
return true;
}
}
);
$( 'form' ).parsley(
'addListener', {
onFieldError: function ( elem ) {
elem.parent().addClass('err');
return true;
}
}
);
It doesn't work fine when I set more validators to one field, for example required and data min. Error message is visible but the class is removed and not set back again.
Thanks for any advice.
I'm designing a custom control to look something like this:
I've got the ComboBox loading the Store fine, but what I'm trying to do is use the arrows to select the next and previous values in the Combo. I've set the valueField on the Combo to be the "id" from the Store, which are not incremental in fashion.
I've tried something like this:
// this gets the current record
var currentBanner = this.bannersComboBox.getStore().getById(this.bannersComboBox.getValue());
// this gets the current records index in the store
var currentStoreIndex = this.bannersComboBox.getStore().indexOf(currentBanner);
The problem is that setValue() on the ComboBox requires the "value", and in this case I need to just do a simple setValue(currentValue + 1 [-1]), or something of that nature. How would you increment the combo when the values aren't incremental?
This would be really easy if there was a selectNext() or something method on the Combo!
Figured it out. :)
var currentBanner = this.bannersComboBox.getStore().getById(this.bannersComboBox.getValue());
var currentStoreIndex = this.bannersComboBox.getStore().indexOf(currentBanner);
var nextBannerValue = this.bannersComboBox.getStore().getAt(currentStoreIndex + 1).get('id')
this.bannersComboBox.setValue(nextBannerValue);
I prefer to use select() rather than setValue(). Here's the code to select the next combobox item:
function comboSelectNextItem( combo, suppressEvent ) {
var store = combo.getStore();
var value = combo.getValue();
var index = store.find( combo.valueField, value );
var next_index = index + 1;
var next_record = store.getAt( next_index );
if( next_record ) {
combo.select( next_record );
if( ! suppressEvent ) {
combo.fireEvent( 'select', combo, [ next_record ] );
}
}
}
Code to select the previous combobox item:
function comboSelectPrevItem( combo, suppressEvent ) {
var store = combo.getStore();
var value = combo.getValue();
var index = store.find( combo.valueField, value );
var prev_index = index - 1;
var prev_record = store.getAt( prev_index );
if( prev_record ) {
combo.select( prev_record );
if( ! suppressEvent ) {
combo.fireEvent( 'select', combo, [ prev_record ] );
}
}
}
You could also extend Ext.form.field.ComboBox to include those functions (with minor changes). For example you can place this code in the launch() function of your application, so that any Ext.form.field.Combobox inherits of those two methods:
Ext.define( "Ext.form.field.ComboBoxOverride", {
"override": "Ext.form.field.ComboBox",
"selectNextItem": function( suppressEvent ) {
var store = this.getStore();
var value = this.getValue();
var index = store.find( this.valueField, value );
var next_index = index + 1;
var next_record = store.getAt( next_index );
if( next_record ) {
this.select( next_record );
if( ! suppressEvent ) {
this.fireEvent( 'select', this, [ next_record ] );
}
}
},
"selectPrevItem": function( suppressEvent ) {
var store = this.getStore();
var value = this.getValue();
var index = store.find( this.valueField, value );
var prev_index = index - 1;
var prev_record = store.getAt( prev_index );
if( prev_record ) {
this.select( prev_record );
if( ! suppressEvent ) {
this.fireEvent( 'select', this, [ prev_record ] );
}
}
}
});
Then you could use those methods anywhere in your code:
var combo = ... // the combo you are working on
combo.selectNextItem(); // select the next item
combo.selectPrevItem(); // select the previous item
I use custom CellEditor in my grid:
getCellEditor: function(colIndex, rowIndex) {
var field = this.getDataIndex(colIndex);
if (field == 'value') {
if ( type == 3 ) {
return this.editors['number'];
} else if ( type == 1 ) {
return this.editors['select'];
} else if ( type == 4 ) {
return this.editors['checkbox'];
}
}
return Ext.grid.ColumnModel.prototype.getCellEditor.call(this, colIndex, rowIndex);
}
},this);
type - this is record.get('type') from grid.store. How to know type in this getCellEditor ?
(I don't want to use global variable :) )
You can use the rowIndex parameter, so to access the 'type' for the current row in your grid:
grid.store.getAt(rowIndex).data.type
You will have need to have defined the type in the field definition, From there, you can use the row index to get the Record.
var record = store.getAt(rowIndex);
for(var i = 0; i<record.fields.length; i++) {
if(record.fields[i].name == 'Your Field') {
alert(record.fields[i].type);
}
}
This is untested but shows how you can test for the type of a given field in the record