I'm trying to create a custom plugin to insert an image from my already built media browser. I'd like to attach some attributes to the image. No matter what I try it only inserts the image with the src and alt attribute. In other words my image is always missing the data-source and class attribute. I've tried the data attribute key as dataSource but that also doesn't work.
const imageElement = writer.createElement( 'image', {
'src': src,
'alt': alt,
'data-sources': dataSources,
'class': cls
} );
editor.model.insertContent( imageElement, editor.model.document.selection );
Any ideas or suggestions would be greatly appreciated.
You need to make 2 things to process new attributes of an image.
First, you need to extend the schema with proper rules which inform model that given attributes are allowed in the editor.
Second thing is to inform the editor how to convert given attribute to model structure and vice-versa with proper converters.
Unfortunately, image converters are quite complicated, because the image is always wrapped with <figure>. Below you can find code and link to working sample how you can create such converters (converters are created based on the source code of the image plugin for CKEditor5). For the purpose of this sample, the data-source attribute is stored in the model as dSource attribute of an image element.
editor.model.schema.extend( 'image', { allowAttributes: 'dSource' } );
editor.conversion.for( 'upcast' ).attributeToAttribute( {
view: 'data-source',
model: 'dSource'
} );
editor.conversion.for( 'downcast' ).add( dispatcher => {
dispatcher.on( 'attribute:dSource:image', ( evt, data, conversionApi ) => {
if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
return;
}
const viewWriter = conversionApi.writer;
const figure = conversionApi.mapper.toViewElement( data.item );
const img = figure.getChild( 0 );
if ( data.attributeNewValue !== null ) {
viewWriter.setAttribute( 'data-source', data.attributeNewValue, img );
} else {
viewWriter.removeAttribute( 'data-source', img );
}
} );
} );
Link to working sample: https://codepen.io/msamsel/pen/pXKRed?editors=1010
Related
How would I adapt #ghettovoice JSFiddle that saves a map to PDF to save the map to a JPEG or PNG? I have no idea how to attempt this problem so ideally if you know hoe to do it you can explain the logic behind it.
exportMap: function () {
var map = this.$refs.map
map.once('rendercomplete', function () {
var mapCanvas = document.createElement('canvas');
var size = map.getSize();
mapCanvas.width = size[0];
mapCanvas.height = size[1];
var mapContext = mapCanvas.getContext('2d');
Array.prototype.forEach.call(
document.querySelectorAll('.ol-layer canvas'),
function (canvas) {
if (canvas.width > 0) {
var opacity = canvas.parentNode.style.opacity;
mapContext.globalAlpha = opacity === '' ? 1 : Number(opacity);
var transform = canvas.style.transform;
// Get the transform parameters from the style's transform matrix
var matrix = transform
.match(/^matrix\(([^(]*)\)$/)[1]
.split(',')
.map(Number);
// Apply the transform to the export map context
CanvasRenderingContext2D.prototype.setTransform.apply(
mapContext,
matrix
);
mapContext.drawImage(canvas, 0, 0);
}
}
);
if (navigator.msSaveBlob) {
// link download attribuute does not work on MS browsers
navigator.msSaveBlob(mapCanvas.msToBlob(), 'map.png');
} else {
var link = document.getElementById('image-download');
link.href = mapCanvas.toDataURL();
link.click();
}
});
map.renderSync();
}
The problem was a combination of missing dependencies (namely FileSaver.js and fakerator.js) and a cross origin server block (CORS block) (Browsers automatically prevent httpRequests to a different domain name unless the server allows it). The first one is fixed by installing the packages while the second one is resolved by setting the crossOrigin Attribute of the ImageWMSLayer to null in my case but possibly to 'Anonymous' for other sources. Hope this helped someone else :)
I have the following code to insert Text at current position:
editor.model.change( writer => {
editor.model.insertContent( writer.createText('[Insert]') );
});
This works fine in most case like inserting inside a paragraph or headline.
Example given:
Before:
<h2>Sample</h2>
After insertion:
<h2>Samp[Insert]le</h2>
But if the text is preformatted for example with a custom font size it breaks the html element:
Before:
<p><span class="text-huge">Sample formatted text</span></p>
After insertion:
<p><span class="text-huge">Sample fo</span>[Insert]<span class="text-huge">rmatted text</span></p>
Notice, that the Element is split up and the text is inserted without applying the custom styles. The [Insert] is set between two spans...
How can I Insert a text directly without modifying html structure?
This happens because created text node has no attributes set. To do this you need to get current selection's attributes and pass it to the writer.createText() method. Then the created text node will have those attributes:
const model = editor.model;
model.change( writer => {
const currentAttributes = model.document.selection.getAttributes();
model.insertContent( writer.createText( '[Foo]', currentAttributes ) );
} );
Alternatively you can use writer.insertText() method if you're inserting text:
editor.model.change( writer => {
const selection = editor.model.document.selection;
const currentAttributes = selection.getAttributes();
const insertPosition = selection.focus;
writer.insertText( '[Foo]', currentAttributes, insertPosition );
} );
I'm trying to create a simple widget test in Flutter. I have a custom widget that receives some values, composes a string and shows a Text with that string. I got to create the widget and it works, but I'm having trouble reading the value of the Text component to assert that the generated text is correct.
I created a simple test that illustrates the issue. I want to get the text value, which is "text". I tried several ways, if I get the finder asString() I could interpret the string to get the value, but I don't consider that a good solution. I wanted to read the component as a Text so that I have access to all the properties.
So, how would I read the Text widget so that I can access the data property?
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('my first widget test', (WidgetTester tester) async {
await tester
.pumpWidget(MaterialApp(home: Text("text", key: Key('unit_text'))));
// This works and prints: (Text-[<'unit_text'>]("text"))
var finder = find.byKey(Key("unit_text"));
print(finder.evaluate().first);
// This also works and prints: (Text-[<'unit_text'>]("text"))
var finderByType = find.byType(Text);
print(finderByType.evaluate().first);
// This returns empty
print(finder.first.evaluate().whereType<Text>());
// This throws: type 'StatelessElement' is not a subtype of type 'Text' in type cast
print(finder.first.evaluate().cast<Text>().first);
});
}
I got it working. I had to access the widget property of the Element, and then cast it as text:
var text = finder.evaluate().single.widget as Text;
print(text.data);
Please check this simple example.
testWidgets('Test name', (WidgetTester tester) async {
// findig the widget
var textFind = find.text("text_of_field");
// checking widget present or not
expect(textFind, findsOneWidget);
//getting Text object
Text text = tester.firstWidget(textFind);
// validating properies
expect(text.style.color, Colors.black);
...
...
}
You can use find.text
https://flutter.io/docs/cookbook/testing/widget/finders#1-find-a-text-widget
testWidgets('finds a Text Widget', (WidgetTester tester) async {
// Build an App with a Text Widget that displays the letter 'H'
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: Text('H'),
),
));
// Find a Widget that displays the letter 'H'
expect(find.text('H'), findsOneWidget);
});
In one of my projects I use a dijit.Tree control. I need to add a search to the tree and show only those nodes/leafs which have the searched term in them. However I can't seem to figure out how that can be achieved. Could anybody please help me?
im not entirely sure that your question entirely but it should give hint whereas to go.
Lets use reference documentation example as offset, there is 1) a store 2) a model and 3) the tree
var store = new ItemFileReadStore({
url: "{{dataUrl}}/dijit/tests/_data/countries.json"
});
var treeModel = new ForestStoreModel({
store: store,
query: {"type": "continent"}, // note, this bit
rootId: "root",
rootLabel: "Continents",
childrenAttrs: ["children"]
});
new Tree({
model: treeModel
}, "treeOne");
Interpret the above as such; You have loaded all known countries and continents but 'user' has selected only to show continents by using query on the model - and the hierachy is then represented in a tree structure.
You want a textbox with searching capeabilities, so we hook into onChange
new dijit.form.TextBox({
onChange: function() {
...
}
});
First bit, getting variables
var searchByName = this.get("value");
var oQuery = treeModel.query;
Next, set a new query on the model - preserving the old ones with an object mixin
treeModel.query = dojo.mixin(oQuery, { name: '*'+searchByName+'*' });
Last, notify the model and its tree that changes has occurred - and requery the visible items.
treeModel._requeryTop();
NB If the top-level item (for ForestModel) is not visible, none of its child elements will show, even if the search-string matches those. (Examplewise, Alabama is not shown if US Continent is not matched by query)
EDIT
As OP has the agenda to go by the 'NB', this may not fit needs 100% but its what dojo offers with dijit.Tree.. As it will get rather a lengthy process to recode the model/store queries to include parentbranches up until root i will not do this here - but there are a few tricks still ;)
var tree = new dijit.Tree( {
/**
* Since TreeNode has a getParent() method, this abstraction could be useful
* It sets the dijit.reqistry id into the item-data, so one l8r can get parent items
* which otherwise only can be found by iterating everything in store, looking for item in the parent.children
*
*/
onLoad : function() {
this.forAllNodes(function(node) {
// TreeNode.item <-- > store.items hookup
node.item._NID = node.domNode.id
});
},
/* recursive iteration over TreeNode's
* Carefull, not to make (too many) recursive calls in the callback function..
* fun_ptr : function(TreeNode) { ... }
*/
forAllNodes : function(parentTreeNode, fun_ptr) {
parentTreeNode.getChildren().forEach(function(n) {
fun_ptr(n);
if(n.item.children) {
n.tree.forAllNodes(fun_ptr);
}
})
}
});
(non-tested, but might just work) Example:
// var 'tree' is your tree, extended with
tree.forAllNodes = function(parentTreeNode, fun_ptr) {
parentTreeNode.getChildren().forEach(function(n) {
fun_ptr(n);
if(n.item.children) {
n.tree.forAllNodes(fun_ptr);
}
})
};
// before anything, but the 'match-all' query, run this once
tree.forAllNodes(tree.rootNode, function(node) {
// TreeNode.item <-- > store.items hookup
node.item._NID = node.domNode.id
});
// hopefully, this in end contains top-level items
var branchesToShow = []
// run fetch every search (TextBox.onChange) with value in query
tree.model.store.fetch(query:{name:'Abc*'}, onComplete(function(items) {
var TreeNode = null;
dojo.forEach(items, function(item) {
TreeNode = dijit.byId(item._NID+'');
while(TreeNode.getParent()
&& typeof TreeNode.getParent().item._RI == 'undefined') {
TreeNode = TreeNode.getParent();
}
branchesToShow.push(TreeNode.item);
});
}});
// Now... If a success, try updating the model via following
tree.model.onChildrenChange(tree.model.root, branchesToShow);
I am trying to use the accepts_nested_attributes_for method based on the tutorial by Ryan Bates ( http://railscasts.com/episodes/196-nested-model-form-part-1 ).
For my form, however, I want to have a select list made up of the child elements so that the parent form has a list of children. I then want to dynamically add children and populate them to the select box via ajax. I want those children to be created even if the form submit is aborted.
question - is this possible?
thanks!
EDIT: I have the form populating with the children in the select already. The question is really whether the ajax portion will work. Is it possible to submit just a portion of a form?
Turns out I ended up using the jquery autocomplete combobox (http://jqueryui.com/demos/autocomplete/#combobox) with some modifications. Specifically, the change: function listed looks for invalid selections (which is our case if the user types into the combobox). Here's the default code from the widget:
...
change: function( event, ui ) {
if ( !ui.item ) {
...
});
if ( !valid ) {
// remove invalid value, as it didn't match anything
$( this ).val( "" );
select.val( "" );
input.data( "autocomplete" ).term = "";
return false;
}
}
}
...
and I changed it to:
...
change: function( event, ui ) {
if ( !ui.item ) {
...
});
if ( !valid ) {
var _new_text = $(this).val()
var _new_option = '<option value="'+_new_text+'">' + _new_text + '</option>'
select.prepend(_new_option);//append to combobox
//now let's select it for submission
select.children( "option" ).each(function(){
if( $(this).text().match(_new_text) ){
this.selected = true;
return false;
}
})
}
}
}
...
Not sure if there's a better way to do it, but this is where I'm at now! Now working on the filtering of the content in Rails, but that's another story.