Trigger event when a certain element has been removed - slate.js

I'm building an editor using the excellent Slate editor, but I'm having trouble with a certain task. I've built a drag-and-drop image upload that successfully uploads images (via API, not related to Slate) and inserts them into the editor. However, I want to delete the image from the server if the user deletes it in the editor. Is there a way to trigger functions when a certain node type is removed from the state?

I've just started looking at slate and was looking into the same issue. My solution is to:
create a plugin function for image handling (options) => { /* plugin object */}
In this plugin function, in schema.nodes, return a wrapper around my main Image component that sets an onDelete prop from the options parameter.
//ImagePlugin function
export default function ImagePlugin(options) {
return {
schema: {
nodes: {
image: props => <Image
{...Object.assign({ onDelete: options.onDelete }, props) } />
}
}
}

Related

Vue Mocha test: wrapper.setProps({}) does not update nested Components props

I am the developer of this Vue.js Plugin and am currently working on the test for v1.0.0, using already written tests for older versions with some adjustments.
Scenario
Test the components with the following structure:
// receives props and passes through
VueEllipseProgress
// receives props, adds new and passes through
EpCircleContainer
// receives props and do main SVG rendering
CircleProgress
Use Factory function:
// this is the top level VueEllipseProgress component
import Container from "../../../src/components/VueEllipseProgress.vue";
import Circle from "../../../src/components/Circle/CircleProgress.vue";
const factory = propsData => {
return mount(Container, {
propsData: {
...propsData
}
});
};
const wrapper = factory({...})
Use wrapper.setProps() in the test to apply new props. Test, how changed props affect the rendering of SVG elements on the other end. You can see the whole code on GitHub.
Problem
wrapper.setProps() updates the props of the VueEllipseProgress (top level) correctly and wrapper.vm.$props has the expected values. But the props of CircleProgress component remain unchanged, HTML still not updated. This leads to test failures.
it("do some test", () => {
/* do some test here, all is fine */
wrapper.setProps({ someProp}); // set new props
wrapper.vm.someProp; // updated
circleWrapper.vm.someProp; // still old
// fails!!!
expect(circleWrapper.element.getAttribute("someProp")).to.equal(someProp);
});
Here are more code details related to above example.
Note, that the plugin works correctly live and all props are reactive.
The test worked for earlier versions of my plugin. In the meantime i have updated #vue/cli to version 4.x.x. Maybe the failures are related to this update, but I couldn't find any information in the release notes that could confirm this.
This is not the direct solution, more a tip to avoid the problem. In my components, I use v-binde="$props" to propagate the props to subcomponents. However, this can cause issues with jsdom, like in my case.
I ended up with refactoring all my tests by testing each komponent directly, avoiding the need of nested structure and props propagation (like unit tests are supposed to be).

How to trigger didReceiveAttrs in Ember component

Using version 2.17. I have an Ember component inside an /edit route with a controller:
// edit.hbs
{{ingredient-table recipe=model ingredients=model.ingredients}}
Inside my component, I am using a didRecieveAttrs hook to loop through ingredients on render, create proxy objects based off of each, and then build an ingredient table using those proxy objects.
// ingredient-table.js
didReceiveAttrs() {
let uniqueIngredients = {};
this.get('ingredients').forEach((ingredient) => {
// do some stuff
})
this.set('recipeIngredients', Object.values(uniqueIngredients));
}
I also have a delete action, which I invoke when a user wishes to delete a row in the ingredient table. My delete action looks like this:
// ingredient-table.js
deleteIngredient(ingredient) {
ingredient.deleteRecord();
ingredient.save().then(() => {
// yay! deleted!
})
}
Everything mentioned above is working fine. The problem is that the deleted ingredient row remains in the table until the page refreshes. It should disappear immediately after the user deletes it, without page refresh. I need to trigger the didReceiveAttrs hook again. If I manually call that hook, all my problems are solved. But I don't think I should be manually calling it.
Based on the docs, it is my understanding that this hook will fire again on page load, and on re-renders (not initiated internally). I'm having some trouble figuring out what this means, I guess. Here's what I've tried:
1) calling ingredients.reload() in the promise handler of my save in ingredient-table.js (I also tried recipe.reload() here).
2) creating a controller function that calls model.ingredients.reload(), and passing that through to my component, then calling it in the promise handler. (I also tried model.reload() here).
Neither worked. Am I even using the right hook?
I suppose recipeIngredients is the items listed in the table. If that is the case; please remove the code within didReceiveAttrs hook and make recipeIngredients a computed property within the component. Let the code talk:
// ingredient-table.js
recipeIngredients: Ember.computed('ingredients.[]', function() {
let uniqueIngredients = {};
this.get('ingredients').forEach((ingredient) => {
// do some stuff
})
return Object.values(uniqueIngredients)
})
My guess is didReceiveAttrs hook is not triggered again; because the array ingredients passed to the component is not changed; so attrs are not changed. By the way; do your best to rely on Ember's computed properties whenever possible; they are in the hearth of Ember design.

Using the model hook to load data dependent on another API call

In an app I'm currently developing, I need to load some data before deciding what data to load next and display. My initial approach was to define a route with a dynamic section like this:
this.route( "chart", { path: "/chart/:type" } );
The idea would be to use type to download some metadata and then request the actual chart data based on the metadata. This is a bit awkward in Ember, at least to the extent of my knowledge, as it would require doing an AJAX request in a controller without using ember-data to wrap it.
As the chart will be displayed using the same code independently of the metadata and there are some interface elements that will be rendered based on that metadata, I thought it would be a good idea to use Ember's router to handle the calls doing something like:
this.route( "chart", { path: "/chart/" }, function () {
this.route( "display-chart", { path: "/score/" } );
this.route( "display-chart", { path: "/ranking/" } );
this.route( "display-chart", { path: "/matches/" } );
} );
There is only a limited number of values for the dynamic segment so for each segment I will create a route handled by the same handler. The chart template will load the metadata and render the common UI elements, leaving the actual chart to the display-chart handler.
I have two questions:
Using the first approach, a route with a dynamic segment, is it possible to load the additional data (which depends on first downloading metadata) within the model hook? Something like RSVP but using previously downloaded data to download the next bit.
The second approach, using nested routes, is this the right way to do it? Can you think of any issues with this approach?
You could chain promises in your model hook, i.e.
model() {
return this.get('ajax').request(/*some url*/).then(data => {
/* Now here you can use your requested data
to decide what to load in the store
and then just return that promise back, like this: */
return this.store.findAll('someModel');
/* The result of the model hook
will be the result of the findAll method */
})
}

Creating a component from a plugin's element when plugin is already inside another component

I'm using a third-party plugin and I have no control over it. I'm using Ember in my entire application except for this plugin and I need instantiate a component inside a div rendered by said plugin to control it the way I want (to avoid the use of jQuery events and such).
1- I'm creating a component called "MyNewComponent"
2- The current structure:
<myComponent1>
<myPlugin-notEmber>
<div-where-I-want-to-append-MyNewComponent class="divClass"/>
</myPlugin-notEmber>
</myComponent1>
3- Take into account that the "div-where-I-want-to-append-MyNewComponent" is rendered by the plugin, not by me.
4- What I'm currently trying to do inside myComponent1 is:
onDidInsertElement: Em.on('didInsertElement', function() {
this.$().find('.divClass').each(function(index, element) {
MyNewComponent.create().appendTo(Em.$(element));
});
}),
Why it's not working:
I'm getting this: (Ember 1.13)
"You cannot append to an existing Ember.View. Consider using Ember.ContainerView instead."
What I'm looking for:
a) the right way to do this OR
b) an equivalent alternative (that will create a component inside that plugin)
Your best option would be to use ember-wormhole
Which allows you to place anything inside another div with an id, so:
{{#ember-wormhole to="destination"}}
{{my-new-component}}
{{/ember-wormhole}}
And somewhere else you'd have
<div id="destination"></div>
This will render the {{my-new-component}} inside of that div.

jQuery cycle2 and 'continueAuto'

I have a simple cycle2 slideshow with a pager. The slideshow proceeds automatically (ie. with set speed and timeout). What I want to achieve is, that once the user clicks a link in the pager, the slideshow becomes "manual" (the automatic transitioning stops) and from that point it is controllable solely by the pager.
The cycle2 API documentation says that there is the 'continueAuto' option which should serve the purpose. So I made a handler to the cycle-pager-activated event like this:
$('#fp_slideshow').on('cycle-pager-activated', function(event, opts ) {
$('#fp_slideshow').cycle({continueAuto: false});
});
The event gets called as I expect (upon clicking a link in the pager), but calling cycle({continueAuto: false}) does nothing and the slideshow goes on indefinetely.
What am I doing wrong?
The pause command might work better for you in this instance. I tried using the cycle-pager-activated event but couldn't get it to work consistently so instead attached a click handler directly to the pager links. Here's the JavaScript:
var $fp_slideshow = $('#fp_slideshow');
$fp_slideshow.cycle({
pager: '#fp_slideshow_pager',
pagerTemplate: '<li>Slide {{slideNum}}</li>'
});
$('a', '#fp_slideshow_pager').on('click', function() {
$fp_slideshow.cycle('pause');
});
And here's a fiddle: http://jsfiddle.net/N43KH/1/