Parsley.js adding span before error message - parsley.js

I'd like to add span element before every messages is Parsley.js. This code's working for prepared messages but not for custom messages.
/**
* Add / override error message
*
* #method addMessage
* #param {String} name Message name. Will automatically be binded to validator with same name
* #param {String} message Message
*/
, addMessage: function ( key, message, type ) {
if ( 'undefined' !== typeof type && true === type ) {
this.messages.type[ key ] = '<span class="del2"></span>' + message;
return;
}
// custom types messages are a bit tricky cuz' nested ;)
if ( 'type' === key ) {
for ( var i in message ) {
this.messages.type[ i ] = '<span class="del2"></span>' + message[ i ];
}
return;
}
this.messages[ key ] = '<span class="del2"></span>' + message;
}
};

It is necessary replaced liError[ liClass ] = message; by liError[ liClass ] = '<span class="del2"></span>' + message;

Related

Forge Autodesk load multiple models in Viewer with ReactJS

We are using Forge Autodesk Viewer to load Forge Models.
We are using the framework ReactJS for our application and we have the function bellow to load one model at a viewer :
function loadModel(viewer, documentId) {
function onDocumentLoadSuccess(viewerDocument) {
// viewerDocument is an instance of Autodesk.Viewing.Document
const bubbleNode = viewerDocument.getRoot();
let defaultModel;
if (props.phaseName) {
defaultModel = bubbleNode.getMasterView(props.phaseName);
} else if (props.guid) {
defaultModel = bubbleNode.findByGuid(props.guid);
} else if (props.viewableID) {
const results = bubbleNode.search({viewableID: props.viewableID});
if (results && results.length) {
defaultModel = results[0];
}
} else if (props.geomIndex) {
const geoms = bubbleNode.search({type: "geometry"});
if (geoms.length) {
if (props.geomIndex < 0 || props.geomIndex >= geoms.length) {
console.warn("GeometryIndex Error: Invalid geometry index.");
}
const index = Math.min(Math.max(props.geomIndex, 0), geoms.length - 1); // Ensure index is valid.
defaultModel = geoms[index];
}
}
if (!defaultModel) defaultModel = bubbleNode.getDefaultGeometry(true);
const skipHiddenFragments = props.skipHiddenFragments || false;
viewer.loadDocumentNode(viewerDocument, defaultModel, {
keepCurrentModels: true,
skipHiddenFragments: skipHiddenFragments,
});
viewer.prefs.set("ghosting", false);
viewer.loadExtension("Autodesk.Viewing.MarkupsCore")
viewer.loadExtension("Autodesk.Viewing.MarkupsGui")
}
function onDocumentLoadFailure() {
console.error("Failed fetching Forge manifest");
}
if (documentId) {
Autodesk.Viewing.Document.load(
documentId,
onDocumentLoadSuccess,
onDocumentLoadFailure
);
} else {
props.eventBus.dispatchEvent({type: "VIEWER_READY", data: {viewer}});
}
}
We actually want to know how we could load multiple models using ReactJS.
Thank you for your response.
There is no difference in making the code support multiple models between w/ and w/o react.js. You can find lots of examples by searching https://stackoverflow.com/search?q=%5Bautodesk-forge%5D+multiple+models
Anyway...
Here is one for loading multiple models, but you must change the props passed to the Viewer component, and event calls outside the Viewer component accordingly.
//process each promise
//refer to http://jsfiddle.net/jfriend00/h3zaw8u8/
const promisesInSequence = (tasks, callback) => {
const results = [];
return tasks.reduce((p, item) => {
return p.then(() => {
return callback(item).then((data) => {
results.push(data);
return results;
});
});
}, Promise.resolve());
};
const AGGREGATE_GEOMETRY_LOADED_EVENT = 'aggregateGeometryLoaded';
/**
* #component
* Component for rendering LMV
* #param {Object} props
* #param {("AutodeskProduction"|"AutodeskStaging"|"MD20ProdUS"|"MD20ProdEU")} [props.env] Forge API environment
* #param {Function} props.getToken Returns the Forge API token to access LMV
* #param {"derivativeV2"|"derivativeV2_EU"|"modelDerivativeV2"|"fluent"|"D3S"|"D3S_EU"} [props.api] Default = "derivativeV2". Please refer to LMV documentation for more information.
* #param {Object[]} [props.docUrns] Model data to be loaded
* #param {string} [modelURNs[].urn] Document URN of the model to be loaded
* #param {Object} [modelURNs[].options] model options used in loading the specfic model
* #param {string} [modelURNs[].options.phaseName] phaseName of view to load in scene.
* #param {string} [modelURNs[].options.guid] guid of BubbleNode to load in scene.
* #param {string} [modelURNs[].options.viewableID] viewableID of BubbleNode to load in scene.
* #param {number} [modelURNs[].options.geomIndex] Index of geometry to load in scene.
* #param {Boolean} [modelURNs[].options.skipHiddenFragments] Boolean to specify if hidden fragments should be skipped (Default: false,
* Hidden fragments are required for heatmaps in rooms, only applicable to SVF2)
* #param {OnModelLoaded} [props.onModelLoaded] Callback function invoked when the model has loaded
* #param {OnViewerInitialized} [props.onViewerInitialized] Callback function invoked when LMV has been intialized
* #param {string[]} [props.extensions] List of extension ids forwarded to viewer config to load.
* #param {Object.<string, Object>} [props.disabledExtensions] Default extensions to prevent being loaded.
* #param {Object} [props.viewerOptions] Options object to forward to Autodesk.Viewing.Initializer
* #memberof Autodesk.DataVisualization.UI
* #alias Autodesk.DataVisualization.UI.Viewer
*/
export default function Viewer(props) {
const viewerRef = useRef(null);
const viewerDomRef = useRef(null);
function onModelLoaded(event) {
const viewer = viewerRef.current;
// const av = Autodesk.Viewing;
// viewer.removeEventListener(av.GEOMETRY_LOADED_EVENT, onModelLoaded);
viewer.removeEventListener(AGGREGATE_GEOMETRY_LOADED_EVENT, onModelLoaded);
if (props.onModelLoaded) {
props.onModelLoaded(viewer, event);
}
}
/**
* Initializes LMV.
*
* #memberof Autodesk.DataVisualization.UI
* #alias Autodesk.DataVisualization.UI.Viewer#initializeViewer
* #private
*/
function initializeViewer() {
let viewerOptions = props.viewerOptions;
var options = Object.assign({}, viewerOptions, {
env: props.env,
api: props.api || "derivativeV2", // for models uploaded to EMEA change this option to 'derivativeV2_EU'
getAccessToken: async function (onTokenReady) {
let token = await props.getToken();
var timeInSeconds = 3600; // Use value provided by Forge Authentication (OAuth) API
onTokenReady(token, timeInSeconds);
},
});
Autodesk.Viewing.Initializer(options, async function () {
const extensionsToLoad = props.extensions;
const extensionsWithConfig = [];
const extensionsWithoutConfig = [];
for (let key in extensionsToLoad) {
const config = extensionsToLoad[key];
if (Object.keys(config).length === 0) {
extensionsWithoutConfig.push(key);
} else {
extensionsWithConfig.push(key);
}
}
const viewer = new Autodesk.Viewing.GuiViewer3D(viewerDomRef.current, {
extensions: extensionsWithoutConfig,
disabledExtensions: props.disabledExtensions || {},
});
extensionsWithConfig.forEach((ext) => {
viewer.loadExtension(ext, extensionsToLoad[ext]);
});
viewerRef.current = viewer;
const startedCode = viewer.start(undefined, undefined, undefined, undefined, options);
if (startedCode > 0) {
console.error("Failed to create a Viewer: WebGL not supported.");
return;
}
// loadModel(viewer, props.docUrn);
await loadModels(viewer, props.docUrns);
if (props.onViewerInitialized) {
props.onViewerInitialized(viewer);
}
});
}
/**
* Loads the specified models into the viewer.
*
* #param {Object} viewer Initialized LMV object
* #param {string} documentId Document URN of the model to be loaded
* #param {Object} options
* #param {string} [options.phaseName] phaseName of view to load in scene.
* #param {string} [options.guid] guid of BubbleNode to load in scene.
* #param {string} [options.viewableID] viewableID of BubbleNode to load in scene.
* #param {number} [options.geomIndex] Index of geometry to load in scene.
* #param {Boolean} [options.skipHiddenFragments] Boolean to specify if hidden fragments should be skipped (Default: false,
* Hidden fragments are required for heatmaps in rooms, only applicable to SVF2)
* #memberof Autodesk.DataVisualization.UI
* #alias Autodesk.DataVisualization.UI.Viewer#loadModelAsync
* #private
*/
async function loadModelAsync(viewer, documentId, options) {
return new Promise((resolve, reject) => {
async function onDocumentLoadSuccess(viewerDocument) {
// viewerDocument is an instance of Autodesk.Viewing.Document
const bubbleNode = viewerDocument.getRoot();
let defaultModel;
if (options.phaseName) {
defaultModel = bubbleNode.getMasterView(options.phaseName);
} else if (options.guid) {
defaultModel = bubbleNode.findByGuid(options.guid);
} else if (options.viewableID) {
const results = bubbleNode.search({ viewableID: options.viewableID });
if (results && results.length) {
defaultModel = results[0];
}
} else if (options.geomIndex) {
const geoms = bubbleNode.search({ type: "geometry" });
if (geoms.length) {
if (options.geomIndex < 0 || options.geomIndex >= geoms.length) {
console.warn("GeometryIndex Error: Invalid geometry index.");
}
const index = Math.min(Math.max(options.geomIndex, 0), geoms.length - 1); // Ensure index is valid.
defaultModel = geoms[index];
}
}
if (!defaultModel) defaultModel = bubbleNode.getDefaultGeometry(true);
const skipHiddenFragments = options.skipHiddenFragments || false;
let model = await viewer.loadDocumentNode(viewerDocument, defaultModel, {
keepCurrentModels: true,
skipHiddenFragments: skipHiddenFragments,
});
// modify the preference settings, since ghosting is causing heavy z-fighting with the room geometry
// it would be good we turn it off
if (!viewer.model)
viewer.prefs.set("ghosting", false);
await viewer.waitForLoadDone();
resolve(model);
}
function onDocumentLoadFailure() {
console.error("Failed fetching Forge manifest");
}
if (documentId) {
Autodesk.Viewing.Document.load(
documentId,
onDocumentLoadSuccess,
onDocumentLoadFailure
);
} else {
props.eventBus.dispatchEvent({ type: "VIEWER_READY", data: { viewer } });
}
});
}
/**
* Loads the specified models into the viewer.
*
* #param {Object} viewer Initialized LMV object
* #param {Object[]} modelURNs Model data to be loaded
* #param {string} [modelURNs[].urn] Document URN of the model to be loaded
* #param {Object} [modelURNs[].options] model options used in loading the specfic model
* #param {string} [modelURNs[].options.phaseName] phaseName of view to load in scene.
* #param {string} [modelURNs[].options.guid] guid of BubbleNode to load in scene.
* #param {string} [modelURNs[].options.viewableID] viewableID of BubbleNode to load in scene.
* #param {number} [modelURNs[].options.geomIndex] Index of geometry to load in scene.
* #param {Boolean} [modelURNs[].options.skipHiddenFragments] Boolean to specify if hidden fragments should be skipped (Default: false,
* Hidden fragments are required for heatmaps in rooms, only applicable to SVF2)
* #memberof Autodesk.DataVisualization.UI
* #alias Autodesk.DataVisualization.UI.Viewer#loadModelAsync
* #private
*/
async function loadModelsAsync(viewer, modelURNs) {
// const av = Autodesk.Viewing;
// viewer.addEventListener(av.GEOMETRY_LOADED_EVENT, onModelLoaded, { once: true });
viewer.addEventListener(AGGREGATE_GEOMETRY_LOADED_EVENT, onModelLoaded, { once: true });
const results = await promisesInSequence(modelURNs, (d) => loadModelAsync(d.urn, d.options));
viewer.fireEvent({
type: AGGREGATE_GEOMETRY_LOADED_EVENT,
models: results
});
}
useEffect(() => {
initializeViewer();
return function cleanUp() {
if (viewerRef.current) {
viewerRef.current.finish();
}
};
}, []);
return <div id="forgeViewer" ref={viewerDomRef}></div>;
}
Viewer.displayName = "Viewer";

React function not recognized

In my react app, I have an onClick function that isn't being recognized (TypeError: _this2.click is not a function) when called from dynamically-generated components. I poked around for issues with functions not being bound correctly, but they seem to be. Here's the code:
class C extends React.Component {
constructor(props) {
super(props);
// Bind components
this.eventComponent = this.eventComponent.bind(this);
this.click = this.click(this);
}
/**
* Click function for when a user selects their choice
* #param {[int]} id [id of the event the user is selecting]
*/
click(id) {
console.log(id)
}
/**
* Draws an event component (dynamically generated)
* #param {[String]} name [name of the event]
* #param {[String]} summary [summary of event]
* #return {[Object]} [react element of an event]
*/
eventComponent(name, summary, id) {
if (name != null && summary != null) {
return (
<div >
<h1>{name}</h1>
<p>{summary}</p>
<button onClick={() => this.click(id)}>Here is a button!</button>
</div>
);
}
}
render() {
var event = this.state.event
var objArray = this.state.objArray
var eventMap;
if (event) {
eventMap = objArray.map(function(event) {
// Get first property
var firstProp;
var k;
for(var key in event) {
if(event.hasOwnProperty(key)) {
firstProp = event[key];
k = key;
break;
}
}
return this.eventComponent(firstProp.title, firstProp.summary, k);
}.bind(this))
} else {
eventMap = <p>No events found!</p>;
}
// Generate a default HTML object
var eventComponent = (
<div>
{eventMap}
</div>
);
return eventComponent;
}
}
in your constructor correct this this.click = this.click(this);
to this.click = this.click.bind(this);
The most easy and convenient way is to use arrow functions, so you don't need to do binding in constructor anymore, a lot easier, isn't it?
so just remove this from constructor:
this.click = this.click.bind(this);
and change your function to:
click = (id) => {
console.log(id)
}
As answered by Vikas,
either you can follow that approach or you can use arrow syntax for functions using which there will be no need to bind functions.
Eg.
Click = (Id) => {
} .

ckeditor pasting from google docs removes the styles

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;
}
} )();

Remove invalid values . Backbone validate()

My example :
var UserModel = Backbone.Model.extend({
validate: function(attrs) {
if (attrs.color == 'muave') {
return true;
}
}
});
var me = new UserModel({ id: '123', silent: true });
me.on('invalid', function (model, error) {
console.log('event invalid')
});
me.set('color', 'muave');
me.set('name', 'sasha');
if(!me.isValid()){
me.clear()
}
I do not want to cleane all model / only the properties that do not pass validation.
It's possible ?
thank you very much
UPD:
I do not know how good the way I did?
var UserModel = Backbone.Model.extend({
validate_prop :['color','name'],
validate: function(attrs) {
if (attrs.color == 'muave' || attrs.name == 'sasha') {
return true;
}
},
positiveValid: function(){
for(var i = 0 ;i< this.validate_prop.length;i++){
this.unset(this.validate_prop[i])
}
}
});
var me = new UserModel({ id: '123', silent: true });
me.on('invalid', function (model, error) {
console.log('event invalid')
});
me.set('color', 'muave');
me.set('name', 'sasha');
if(!me.isValid()){
me.positiveValid();
me.isValid() //clear validationError: true
}
Maybe there is a more universal solution?
thank you for your help .
This is roughly how I would go about it (note this is pseudo code I may have a mistake in there):
// An object of validation rules
validation: {
color: {
type: 'String',
options: {
notEqualTo: 'muave'
}
},
name: {
type: 'String',
options: {
notEqualTo: 'sasha'
}
},
},
initialize: function () {
this.validation = validation || {};
},
validate: function (attrs) {
this.invalidAttrs = {};
// Loop through validation object, return the first invalid attribute.
var invalid = _.find(this.validation, function (options, attr) {
var type = attr.type || 'String';
return this['validate' + attr.type](attr, options);
}, this);
// If there was an invalid attribute add it invalidAttrs.
// Also trigger an event.. could be useful.
if (invalid) {
this.invalidAttrs[attr] = invalid;
this.trigger('invalid:' + attr, invalid);
}
return invalid;
},
validateString: function (attr, options) {
if (options.notEqualTo && this.get(attr) === options.notEqualTo) {
return {
'String cannot be equal to ' + options.notEqualTo
};
}
}
Then you could do:
if(!me.isValid()){
_.each(me.invalidAttrs, function(reason, attr) {
me.unset(attr);
});
}
This would only find the error and then stop, you could quite easily change it to accumulate all errors. The fundamental thing is that you have a validation object you loop through which has validation rules for your model. When you say type: 'Number' in that object it will call validateNumber(attr, options) to determine if a number is valid. I would also recommend adding required: true/false for all types.
Here is a more involved and real world example of what I was talking about:
////////////////
// Validation //
////////////////
/**
* Syntax for validation rules
* #type {Object|Function}
* validation: {
* <attributeNameA>: {
* required: false, // default, can be omitted
* type: 'string', // default, can be omitted
* options: {
* // type-specific validation rules, see validator
* // documentation
* }
* },
* <attributeNameB>: {
* validator: function (value, attrs) {
* // optional custom validator, validated first if present
* },
* required: true, // validated second if present
* type: 'date', // type validation is validated last
* options: {
* // type-specific validation rules
* }
* }
* }
*/
/**
* Validate model
* #param {Object} attrs attributes
* #param {Object} options options
* #return {Object|undefined} returns validation error object
* or undefined if valid
*/
validate: function(attrs, options) {
if (!this.validation) {
return;
}
options = options || {};
var invalidAttrs = {},
validAttrs = {},
isValid = true,
isPartial,
validation = _.result(this, 'validation');
function checkValidity(rule, attr) {
/* jshint validthis:true */
var error = rule && this.validateAttr(rule, attr, attrs[attr], options);
if (error) {
invalidAttrs[attr] = error;
isValid = false;
} else if(rule) {
validAttrs[attr] = true;
}
}
if (options.validateAttr) {
var attr = options.validateAttr;
checkValidity.call(this, validation[attr], attr);
this.attrState = this.attrState || {};
this.attrState[attr] = isValid;
isPartial = _.difference(_.keys(validation), _.keys(this.attrState)).length > 0;
} else {
_.each(validation, checkValidity, this);
}
if (!options.silent) {
isValid = options.validateAttr ? _.all(this.attrState) : isValid;
this.triggerValidationEvents(isValid, validAttrs, invalidAttrs, isPartial);
}
// Note: successful validation needs to return undefined / falsy
return isValid ? void 0 : invalidAttrs;
},
/**
* Validate attribute with a given value
* #param {Object} rule validation rule from validation object
* #param {String} attr attribute name
* #param {Mixed} value value
* #param {Object} options options
* #return {Object|undefined} returns validation error object
* or undefined if valid
*/
validateAttr: function(rule, attr, value, options) {
var error;
options = _.extend({}, options, rule.options);
if (rule.required && (value == null || value === '')) {
return {
reasonCode: 'required'
};
}
if (rule.type) {
if (!this['validate' + rule.type]) {
console.error('No validation found for type:', rule.type);
}
error = this['validate' + rule.type].call(this, value, options);
if (error) {
return error;
}
}
if (rule.validator) {
return rule.validator.call(this, value, options);
}
},
/**
* Convenience method: check if a single attribute is valid
* #param {String} attr attribute name to be validated
* #return {Boolean} true if valid
*/
isValidAttr: function(attr, value, options) {
var obj = {};
obj[attr] = value;
var attrs = _.extend({}, this.attributes, obj);
var error = this.validate(attrs, _.extend({}, options, {
validateAttr: attr
}));
if (error) {
this.validationError = this.validationError || {};
this.validationError[attr] = error;
} else if (this.validationError && attr in this.validationError) {
delete this.validationError[attr];
}
return error === void 0;
},
/**
* Triggers events for valid and invalid attributes after validation
* #param {Boolean} isValid was validation successful?
* #param {Object} validAttrs attributes that were validated successfully.
* #param {Object} invalidAttrs attributes for which validation failed.
* #param {Boolean} isPartial is it validating only a part of the model?
*/
triggerValidationEvents: function(isValid, validAttrs, invalidAttrs, isPartial) {
if (!isPartial) {
this.trigger('validated', isValid, this, invalidAttrs);
this.trigger('validated:' + (isValid ? 'valid' : 'invalid'), this, invalidAttrs);
}
_.each(validAttrs, function(value, attr) {
this.trigger('validated:' + attr, this, true);
}, this);
_.each(invalidAttrs, function(error, attr) {
this.trigger('validated:' + attr, this, false, error);
}, this);
}

ExtJS - null safe retrieval of complex objects using JsonReader

I am using a JsonReader to map Json data to variables to be used in a grid/form. The back end is in Java and there are complex objects which I Jsonify and pass to the ExtJS front end.
This is a part of my JsonReader which tries to retrieve a nested object -
{name:'status', type: 'string', mapping: 'status.name'}
This works fine when status has a value (not null in the server), but the grid load fails when status is null. Currently the work around I have is to send an empty object from the server in case of null, but I assume there should be a way to handle this in ExtJS. Please suggest a better solution on the ExtJS side.
I can think of two possibilities - one documented and one undocumented:
use the convert()-mechanism of Ext.data.Field:
{
name:'status',
mapping: 'status',
convert: function(status, data) {
if (!Ext.isEmpty(status) && status.name) {
return status.name;
} else {
return null;
}
}
}
The mapping property can also take an extractor function (this is undocumented so perhaps it may be a little bit risky to rely on this):
{
name:'status',
mapping: function(data) {
if (data.status && data.status.name) {
return data.status.name;
} else {
return null;
}
}
}
Use this safe json reader instead:
Ext.define('Ext.data.reader.SafeJson', {
extend: 'Ext.data.reader.Json',
alias : 'reader.safe',
/**
* #private
* Returns an accessor function for the given property string. Gives support for properties such as the following:
* 'someProperty'
* 'some.property'
* 'some["property"]'
* This is used by buildExtractors to create optimized extractor functions when casting raw data into model instances.
*/
createAccessor: function() {
var re = /[\[\.]/;
return function(expr) {
if (Ext.isEmpty(expr)) {
return Ext.emptyFn;
}
if (Ext.isFunction(expr)) {
return expr;
}
if (this.useSimpleAccessors !== true) {
var i = String(expr).search(re);
if (i >= 0) {
if (i > 0) { // Check all property chain for existence. Return null if any level does not exist.
var a = [];
var l = expr.split('.');
var r = '';
for (var w in l) {
r = r + '.' + l[w];
a.push('obj' + r);
}
var v = "(" + a.join(" && ") + ") ? obj." + expr + " : null";
return Ext.functionFactory('obj', 'return (' + v + ')');
} else {
return Ext.functionFactory('obj', 'return obj' + expr);
}
}
}
return function(obj) {
return obj[expr];
};
};
}()
});
I have changed Slava Nadvorny's example so that it completely works for ExtJS 4.1.1.
New extended class of Ext.data.reader.Json is below:
Ext.define('Ext.data.reader.SafeJson', {
extend: 'Ext.data.reader.Json',
alias : 'reader.safejson',
/**
* #private
* Returns an accessor function for the given property string. Gives support for properties such as the following:
* 'someProperty'
* 'some.property'
* 'some["property"]'
* This is used by buildExtractors to create optimized extractor functions when casting raw data into model instances.
*/
createAccessor: (function() {
var re = /[\[\.]/;
return function(expr) {
if (Ext.isEmpty(expr)) {
return Ext.emptyFn;
}
if (Ext.isFunction(expr)) {
return expr;
}
if (this.useSimpleAccessors !== true) {
var i = String(expr).search(re);
if (i >= 0) {
if (i > 0) { // Check all property chain for existence. Return null if any level does not exist.
var a = [];
var l = expr.split('.');
var r = '';
for (var w in l) {
r = r + '.' + l[w];
a.push('obj' + r);
}
var v = "(" + a.join(" && ") + ") ? obj." + expr + " : null";
return Ext.functionFactory('obj', 'return (' + v + ')');
} else {
return Ext.functionFactory('obj', 'return obj' + (i > 0 ? '.' : '') + expr);
}
}
}
return function(obj) {
return obj[expr];
};
};
}()),
/**
* #private
* #method
* Returns an accessor expression for the passed Field. Gives support for properties such as the following:
*
* - 'someProperty'
* - 'some.property'
* - 'some["property"]'
*
* This is used by buildExtractors to create optimized on extractor function which converts raw data into model instances.
*/
createFieldAccessExpression: (function() {
var re = /[\[\.]/;
return function(field, fieldVarName, dataName) {
var me = this,
hasMap = (field.mapping !== null),
map = hasMap ? field.mapping : field.name,
result,
operatorSearch;
if (typeof map === 'function') {
result = fieldVarName + '.mapping(' + dataName + ', this)';
} else if (this.useSimpleAccessors === true || ((operatorSearch = String(map).search(re)) < 0)) {
if (!hasMap || isNaN(map)) {
// If we don't provide a mapping, we may have a field name that is numeric
map = '"' + map + '"';
}
result = dataName + "[" + map + "]";
} else {
if (operatorSearch > 0) {
var a = [];
var l = map.split('.');
var r = '';
for (var w in l) {
r = r + '.' + l[w];
a.push(dataName + r);
}
result = "("+a.join(" && ")+") ? "+dataName+"."+map+" : null";
} else {
result = dataName + map;
}
}
return result;
};
}())
});
So you can successfully processing nested JSON-data with null nodes.
Example of JSON:
{
root: [{
id: 1,
name: {
name: "John",
phone: "123"
},
},
{
id: 4,
name: null,
},
]
}
Working example with test data you can find here:
http://jsfiddle.net/8Ftag/

Resources