I've made a component that copies some code from a code box. The component javascript looks like:
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'code',
classNames: ['lm-code-box'],
dataTarget: null,
dataTrigger: Ember.computed('dataTarget',
function() {
return `.${this.get('dataTarget')}`;
}
),
copyAction: null,
icon: 'ion-code',
copyStatus: null,
buttonText: 'Copy',
didInsertElement() {
this.clipboard = new Clipboard('.lm-button--copy');
this.clipboard.on('success',(e) => {
this.set('icon','ion-checkmark');
this.set('copyStatus','success');
this.set('buttonText','Copied');
e.clearSelection();
});
this.clipboard.on('error',(e) => {
this.set('icon','ion-android-warning');
this.set('copyStatus','error');
this.set('buttonText','Error');
});
},
willDestroyElement() {
this.clipboard.destroy();
}
});
Component code looks like:
<a class="lm-button--copy {{buttonClass}}" data-clipboard-target={{dataTrigger}} data-clipboard-action={{copyAction}}>
{{buttonText}}
<i class="icon {{icon}}"></i>
</a>
<pre class="{{dataTarget}}">
{{yield}}
</pre>
Then in my template the code looks like:
{{#lm-code-copy dataTarget="testOne"
copyAction="copy"}}
test one
{{/lm-code-copy}}
{{#lm-code-copy dataTarget="testTwo"
copyAction="copy"}}
test two
{{/lm-code-copy}}
Everything copies fine, but in the block:
this.set('icon','ion-checkmark');
this.set('copyStatus','success');
this.set('buttonText','Copied');
changes those key values on both components that are rendered. How can I tell ember to only change the value for the current component? I assumed this would set that context but it doesn't seem to do the trick.
I'll take a chance here, since you didn't supply your component template. I think you're problem might be with your CSS selector
this.clipboard = new Clipboard('.lm-button--copy');
You're always targeting all .lm-button--copy elements in the page with that selector. Meaning that each component instance will have a separate this.clipboard reference but all pointing to the same dom element.
Also the this you refer to is not the component:
this.clipboard.on('success',(e) => { <--- This `this` is your component
this.set('icon','ion-checkmark');
this.set('copyStatus','success'); <---- These `this` are the context of the invoking success handler (you can set a break point here to see its not the ember component)
this.set('buttonText','Copied');
e.clearSelection();
});
You might want something like this (assuming this Clipboard thing can also receive a dom element):
this.clipboard = new Clipboard(this.$('.lm-button--copy'));
In an Ember component this.$ refers to the outer div that wraps the component. As such you will only select elements that are within the component. Which I think what you might need.
#Pedro Rio was close. Using clipboard.js you have to pass in a DOM element similar to jquery using syntanx like clipboard = new Clipboard('.class-name') or clipboard = new Clipboard('#id-name'). Somehow, in Ember world the scope of this was bound to the scope of Clipboard.js's query. So the fix was to use Ember's jQuery syntax to scope clipboard to each button item.
this.clipboard = new Clipboard(this.$(`.lm-button--copy`).get(0));
Please note that there is no . after this.$ as there is in the other answer.
Related
I am writing an app for angular 2 with ES5.
I want to have a component with dynamically loaded view child, that will load existing components.
I saw examples in TypeScript but I am failing do that in ES5, and to inject ng.core.ViewChild in component constructor based on a ng.core.Directive and update the contents of the DOM element ( marked with that directive) with a dynamically loaded, existent, component.
I've tried with
queries:[
formBody: ng.core.ViewChild('formBody')
]
...and I get a ElementRef, but would need a ViewContainerRef to update DOM contents with a dynamically loaded component.
I've tried :
queries:[
formBody: ng.core.ViewChild(app.FormBodyDirective)
]
... but I get a "empty" object. __proto__ object
Component is loaded like this:
ngAfterViewInit: function() {
var dialogComponentFactory = this.componentResolver.resolveComponentFactory(app.FormBody1_Component);
this.formBody = this.formBody.createComponent(dialogComponentFactory);
},
I have tried to inject ng.core.ViewContainerRef into component constructor:
.Class({
constructor: [
ng.core.ViewContainerRef,
function(viewContainer){
this.formBody = viewContainer
}],
but this of course injects a instance of ng.core.ViewContainerRef for my 'qform' element, and I get the dynamically loaded component at the end of the 'qform' element
Link to plunker with my code (not working) http://plnkr.co/edit/mRxGKYvKy8tHRjupNzju?p=preview
I would be very grateful if someone would help me sort this out, or throw a hint..
Thanks !
I finally find a solution. I am not sure that this is the best and most elegant way, but it works.
I injected ng.core.ViewContainerRef, in directive constructor and saved it's instance into class member.
The directive class member was accessible from within component. It was made available through component's queries option.
The directive:
app.FormBodyDirective =
ng.core.Directive({
selector: '[formBody]'
}).Class({
constructor: [
ng.core.ViewContainerRef,
function(viewContainer){
this._viewContainer = viewContainer;
}]
});
The component:
//...
queries : {
formBody: new ng.core.ViewChild(app.FormBodyDirective)
},
directives:[ app.FormBodyDirective ]
//...
Working plunker: http://plnkr.co/edit/ooWhiMqSFDtGNGdW3LMK?p=preview
Here's a very simple Vue app that is passing a prop from the main VM to a component. This value is grabbed via vue-resource in my actual application.
https://jsbin.com/tazuyupigi/1/edit?html,output
Of course it works, but I'm struggling to access this value from the component VM itself.
https://jsbin.com/hurulavoko/1/edit?html,output
As you can see, my alert shows undefined. How can I fix this? Seems to be an issue with order of execution, but using compiled() or created() in place of ready() makes no difference.
It is a problem with order of execution. The main Vue instance isn't "ready" until the things inside it have been compiled. This includes the hello component.
If you need to know that bar has been set before you use it, you can monitor for that in a couple of ways. You could $broadcast an event in the parent to let the child know that bar has been loaded. Or you could use a watch function in the child component to make changes when bar is updated.
Your example does work if you $set bar in created(), however with vue-resource you're going to have a delay anyway, so you need to account for the fact that greeting won't be ready during the child's ready() function. You'll either have to design your DOM structure to handle the fact that greeting could be undefined, or you'll have to use an event or watch function to wait for it yourself.
Example with $broadcast:
Vue.component('hello', {
template: 'From hello tag: {{ greeting }}',
props: ['greeting'],
ready: function() {
},
events:{
'bar-ready':function(){
alert(this.greeting)
}
}
});
new Vue({
el: '#app',
created: function() {
this.$http.get('/path')
.then(function(response){
this.$set('bar',response.data);
this.$broadcast('bar-ready')
}.bind(this))
}
});
$broadcast docs: https://vuejs.org/api/#vm-broadcast
I've built a ractive.js app using partials. These partials are loaded via fetch/ajax - and all works nicely.
I then decided I wanted to encapsulate data along with the partial so looked at components - as I understood a component to do just that: Isolate a template/partial with its data.
I then looked to load the components in: http://ractivejs.github.io/ractive-load/
However, I don't really see the advantage of this approach - as it appears with the loader you can only load in the components template, not the entire encapsulated component (data, templates etc). You still have to put the data onto the main ractive instance (as you would with a partial).
I'm trying to dyanamically update the component. I'm also using page.js for routing. I'm trying to separate out all the concerns.
I'm probably not explaining myself very well - here is my code... most of it was taken from martydpx's answer here How to create Ractive's subcomponents dynamically and change them programmatically )
....
<dynamic name='{{name}}'/>
</script>
<script>
// Component loader
Ractive.load({
home: '/components/home.html', // seems this can only contain a template. Is it possible for it to contain everything - data and all?
packs: '/components/packs.html',
....
addplayer: '/components/addplayer.html',
notfound: '/components/notfound.html',
}).then( function ( components ) {
Ractive.components[ 'home' ] = components.home;
Ractive.components[ 'packs' ] = components.packs;
....
Ractive.components[ 'addplayer' ] = components.addplayer;
Ractive.components[ 'notfound' ] = components.notfound;
// dynamically load component based on route
Ractive.components.dynamic = Ractive.extend({
template: '<component/>',
components: {
component: function() {
this.set('foo','bar'); // I can dynamically set the data here.. but how would I add defaults for each component, within the component?
return this.get('route');
}
},
oninit: function(){
this.observe('route', function(){
this.reset();
},
{ init: false}
);
}
});
var r = new Ractive({
el: document.body,
template: '#template',
data: {
route: 'home'
}
});
// Routing. Sets the route... which triggers the component
page('/', index);
...
page();
function index() {
console.log('index');
r.set('route','home')
}
EDIT
I've read this - which has been a great help :)
https://github.com/ractivejs/component-spec/blob/master/authors.md
In the dynamic component scenario - how would I dynamically update component specific data. I seem to be able to do it when the component tag is hardwired into the page... but not when the component tag is dynamically created. After much playing about in the console - its as if it doesn't see the dynamic component. So things like r.findComponent('home').get() don't work.
Yet, if I put a <home/> tag in the template - it does work.
Also, do components automatically 'tear down' when they're un-rendered?
I'm not 100% sure what you are looking for.
First you create a child component -
var MyWidget = Ractive.extend({
template: '<div>{{message}}</div>',
data: {
message: 'No message specified, using the default'
}
});
You register this with Ractive runtime
Ractive.components.widget = MyWidget;
Then you create a parent component
var Parent = Ractive.extend({
template: '<div>
<MyWidget message={{widget}} />
</div>'
});
You use the parent instance to pass the data to child
// Live instance of parent
new Parent({
el: 'id',
data : {
widget: {
message : 'Waddup kiddo'
}
}
});
data.widget gets mapped to MyWidget's data, in-turn gets the message data.
For more info refer this
Generally there are 3 types of components you will be creating & using -
Self-sufficient Components - It knows everything it needs to know by itself. You don't pass anything to it. It creates it's own data or knows where to get it from. Ex: A logo component which knows by itself where to get the image from.
Dumb Components - They have no intelligence and all the data that it needs should be passed from parent. Like in our example - MyWidget has no idea where and what message stands for. Just renders it. No questions asked. Parent will fetch message and just pass it on.
Smart Components - Components which do some heavy lifting. An example would be Profile component. Parent will pass just a profileID to this, and it knows where to get profile data from, does some ajax calls, knows how to parse and interpret the data, may be even starts a socket and listens to changes etc.
So you decide how you want to make your components, who takes responsibility and think about data-encapsulation then.
Knockout gives you two ways of instantiating a component, either with a custom html element or with the component binding.
However I have discovered a slight issue when trying to style the root component element. It's fine if you just use the custom element syntax as you can just assign css styles to that - however, if you then use the component binding, the css rules don't match and so they fail.
Ideally I want to support both scenarios as they both have their uses. If I could get knockout to add a class to the root component element which is just the component name it would solve the issue but reading the documentation it isn't clear where it would be best to do this.
I've already got a custom template loader which retrieves the template from an ajax call, but this template is just the inner html of the root node.
Basically I want this:
<my-custom-element>
...
...
<my-custom-element>
To become this:
<my-custom-element class="my-custom-element">
...
...
<my-custom-element>
Anyone got any ideas?
You can use "createViewModel" method and access element in the component (e.g. to add some class):
ko.components.register('some-component', {
viewModel: {
createViewModel: function(params, componentInfo) {
var $element = $(componentInfo.element.children[0]);
// some other code ...
}
},
template: "<div></div>"
});
I've been trying to learn Backbone, and I'm developing an app now. But I have a problem with a view's events: App.views.ChannelView should have a click event, but it is not firing.
Here's the code:
http://pastebin.com/GgvVHvtj
Everything get rendered fine, but events won't fire. Setting the el property in the view will work, but I can't use it, and I've seen on Backbone's todo tutorial that it is possible.
How do I make events fire without a defined el property?
You must define the el element to be an existing element in your DOM. If you do not define it, fine, it will default to a div, but when you render the view, the html generated must be appended/prepended whatever, you get the point, to an existing DOM element.
Events are scoped to the view, so something's wrong with your scope. From the code you provided I can't reproduce the problem, so if you might, please provide a live example on jsfiddle/jsbin etc in order to fully understand the issue.
Demo ( in order to demonstrate the view render )
var App = {
collections: {},
models: {},
views: {},
};
App.models.Channel = Backbone.Model.extend({
defaults: {
name: '#jucaSaoBoizinhos'
}
});
App.views.ChannelView = Backbone.View.extend({
el:$('#PlaceHolder'),
events: {
"click .channel": "myhandler"
},
render: function() {
this.$el.html('<div class="channel"><button>' + this.model.get('name') + '</button></div>');
return this;
},
myhandler: function(e) {
alert(e);
console.log(this.model.get('name'));
},
});
var chView = new App.views.ChannelView({model: new App.models.Channel()});
//console.log(chView.render().el) //prints div#PlaceHolder
//without the el specified in the view it would print a div container
//but i would have to render the view into an existing DOM element
//like this
//$('#PlaceHolder').html(chView.render().el)
chView.render()
Can you try doing a
events: {
"all": "log"
}
log: function(e) {
console.log e;
}
That should log out every event that's getting fired. I find it super helpful when troubleshooting.
backbone view events can work without dom element specified. If you can't use any element at the view createion (initialization) moment, then you can use it's 'setElement' method, to attach your view to specified dom element. Here is description.
Be the way your view render method will not work also without specified 'el'.