KnockoutJs Components - add default class - javascript

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

Related

Add HTML object to an event javascript/meteor

How can I add HTML objects to an event?I would like to do something like this:
Template.Schedule.events({
'dblclick .mycol' (event){
event.target.childNodes.append("<strong>Test</strong>");
}
});
I know I could style it and change the innerHTML and so on for the given example, but I actually want to add other HTML objects, like a select-tag, how can I do that?
The vanilla JS way
You could use innerHTML here to change the html content of the clicked element:
'dblclick .mycol' (event){
const target = event.currentTarget
target.innerHTML = target.innerHTML + "<strong>Test</strong>"
}
If you want to manipulate the parent in the event you can use outerHTML
The jQuery way
Your approach of using append is requiring jQuery:
'dblclick .mycol' (event){
$(event.currentTarget).append($("<strong>Test</strong>"))
}
Bonus: Optimization for using jQuery
In a meteor blaze template-events each event has a reference to the template instance. This template instance keeps a reference to a jQuery object and it's part of the DOM that it manipulates.
template.$ returns a jQuery object of those same elements. jQuery
objects are similar to arrays, with additional methods defined by the
jQuery library.
The template instance serves as the document root for the selector.
Only elements inside the template and its sub-templates can match
parts of the selector.
You can access it via templateInstance.$ if your seconds event parameter is namend templateInstance. With
'dblclick .mycol' (event, templateInstance){
templateInstance.$(event.currentTarget).append($("<strong>Test</strong>"))
}
This saves jQuery the need to traverse the whole DOM, makes it more efficient on large documents.
The Meteor Blaze way
Now these are neat little tricks when there is need for manipulation on a small scale. However, you may want your app to be scalable and profit all the time from the Blaze rendering engine.
In such cases you may rather want to generate a way of dynamically inserting templates.
Consider the following template, that is nowhere imported yet:
rowcontent.html
<template name="rowcontent">
<strong>Test</strong>
<p>someData{{someData}}</p>
</template>
rowcontent.js
import './rowcontent.html' // currently just the import
You can dynamically add it at runtime to an element using Blaze.renderWithData so:
'dblclick .mycol' (event, templateInstance) {
import './rowcontent.js' // use the right path here
Blaze.renderWithData(Template.rowcontent, {someData: 'toBePassedToRowContent'}, event.currentTarget)
}
which will result in:
This is my col Test
someDatatoBePassedToRowContent
The advantage of this approach is that you can pass the data to the template and have all the reactive benefits remaining, thus handle the newly added template like any other template in Meteor.
Alternatives
Declarative dynamic templates using Template.dynamic

Angular create Selector Tag <app-component> using variable or loop

I need to make the Selector Tag of my app.component.html using a variable.
Suppose variable name is: componentVar:string
I need my app.component.html:
<componentVar></componentVar> or <app-componentVar></app-componentVar>
You can dynamically create your components within a ViewcontainerRef like this:
https://stackblitz.com/edit/angular-4asbmc
(Don't forget to add the components you want to dynamically inject in your dom in your app module's entryComponents)
Once you are able to insert the components you shoud be able to manage which components have to be inserted into your with conditions.
Edit
You can iterate on an array of component that could look like this:
let componentArray = [HeaderComponent, BannerComponent, FooterComponent];
and then iterate over this array calling the method that inserts your components in your viewcontainer reference.
createComponent(component) {
const factory = this.factoryResolver.resolveComponentFactory(component);
const componentRef = factory.create(yourViewContainerRef.parentInjector);
yourViewContainerRef.insert(componentRef.hostView);
}
Though it would be musch easier to just call your components from your app component html template, and have something like this in your template:
<app-header *ngIf="yourConditions..."></app-header>
<app-banner *ngIf="otherConditions..."></app-banner>
<!-- and so on.... -->

Angular 2 ViewChild implementation with ES5

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

controller for knockout.js custom element

Sometimes a component/custom element has some UI logic which requires some UI code, it's something which can't be done by binding to the component's view-model.
For example, let's say the component needs to change the way it looks based on available space, and this requires manipulating elements by JavaScript code.
What I need is a controller for the UI.
For example, imagine we have a component called myGadget for which I have myGadget.html, myGadgetViewModel.js and I also want to have myGadgetView.js
Within the myGadgetView.js I want to have something like this:
function myGadgetView(element)
{
// element is the custom element's node
}
What is the best way to do this in Knockout?
Should I combine component with custom binding?
With a custom binding I could get access to the element, so the HTML of the component would look like this:
<script id="myBar-template">
<div data-bind="myGadget : ...">
</div>
</script>
and I need to put somewhere this:
ko.bindingHandlers.myGadget = {
init: function (element, valueAccessor)
{
// I have access to element node
var myGadgetView = new myGadgetView(element);
},
update: function (element, valueAccessor)
{
// I have access to element node
}
}
I'm not sure about using custom binding for this, I wonder if there's a better approach.
For example, I'm looking to the custom component loading, but I don't have a clear idea yet.
When defining a component, you can specify a createViewModel function. This function will be passed the element the component will be bound to. According the Knockout documentation, it's still preferable to use custom bindings to manipulate the view.
Any manipulation of the view should be done in binding handlers, but that doesn't mean you can't make something like a jQuery plug-in, which your myGadgetView.js would be, and use that in the binding handler. You just wouldn't want your plug-in to be aware of your viewmodel, nor your viewmodel to be aware of the plug-in. The binding handler would mediate, mapping viewmodel elements to plug-in parameters.

RiotJS - How to pass events between subtags using Observable pattern?

Im not really sure if Im understanding correctly the way observables work and how to get references from mounted tags. I have a component. Within this component we have a component and a component. The purpose is to avoid coupling between components. Because of that, I would like that my search component triggers an event when a search is done(a button is clicked). This event should be caught by the component which will filter the collection data based on the search.
The index.html file load the tag by using:
index.html
riot.mount(".content", "page", null);
The page is defined as follow:
page.js
<page>
<!-- Search tag controls -->
<search id="searchTag"></search>
<!-- Collection data to display -->
<collection id="collectionTag"></collection>
</page>
The component script is briefly defined like:
search.js
var self = this;
riot.observable(self);
<!-- This function is called when the user click on the button. -->
self.filtering = function()
{
<!-- We get data from inputs -->
var info = Getting data from inputs;
<!-- Trigger the event hoping that someone will observe it -->
self.trigger("filterEvent", info);
}
How can I make the component observe for that event?
To me it seems that I should be able to get references from search tag and collection tag in the page.js. By doing so I could connect the events like follow:
searchComponent = riot.mount('search');
collectionComponent = riot.mount('collection');
searchComponent.on('filterEvent', function()
{
<!-- Trigger function to filter collection data -->
collectionComponent.trigger('filterData');
});
Right now I cannot make it work like that.
At the point of execution, searchComponent and collectionComponent are not defined.
I tried also getting references of these component by using this.searchTag and this.collectionTag instead of mounting them but at the time the code is executed, the components have not been mounted and so I dont get a reference to them.
Any ideas to make it work?
Inspired by the answer given by #gius, this is now my preferred method for sending events in RiotJS from one tag to another.. and it is great to work with!
The difference from #gius approach being that, if you use a lot of nested tags, passing a shared Observable to each tag falls short, because you would need to pass it again and again to each child tag (or call up from the child tags with messy this.parent calls).
Defining a simple Mixin, like this (below), that simply defines an Observable, means that you can now share that in any tag you want.
var SharedMixin = {
observable: riot.observable()
};
Add this line to your tags..
this.mixin(SharedMixin);
And now, any tag that contains the above line can fire events like..
this.observable.trigger('event_of_mine');
..or receive events like this..
this.observable.on('event_of_mine',doSomeStuff());
See my working jsfiddle here http://jsfiddle.net/3b32yqb1/5/ .
Try to pass a shared observable to both tags.
var sharedObservable = riot.observable();
riot.mount('search', {observable: sharedObservable}); // the second argument will be used as opts
riot.mount('collection', {observable: sharedObservable});
And then in the tags, just use it:
this.opts.observable.trigger('myEvent');
this.opts.observable.on('myEvent', function() { ... });
EDIT:
Or even better, since your search and collection tags are child tags of another riot tag (page) (and thus you also don't need to mount them manually), you can use the parent as the shared observable. So just trigger or handle events in your child tags like this:
this.parent.trigger('myEvent');
this.parent.on('myEvent', function() { ... });
Firstly I do not understand your file structure !
In your place I would change filenames :
page.js --> page.tag
search.js --> search.tag
And i dont see your search tag in search.js code.
So I dont see your Collection tag file ...
Are you sure that this one use this code ?
riot.observable({self|this});
Because it's him who will receive an Event.
For me when I use Riot.js(2.2.2) in my browser, if I use
searchComponent = riot.mount('search');
searchComponent will be undefined
But with this code you can save your monted tag reference :
var searchComponent ={};
riot.compile(function() {
searchComponent = riot.mount('search')[0];
});
Another option is to use global observables, which is probably not always best practice. We use Riot's built in conditionals to mount tags when certain conditions are met rather than directly mounting them via JS. This means tags are independent of each other.
For example, a single observable could be used to manage all communication. This isn't a useful example on its own, it's just to demonstrate a technique.
For example, in a plain JS file such as main.js:
var myApp = riot.observable();
One tag file may trigger an update.
var self = this;
message = self.message;
myApp.trigger('NewMessage', message);
Any number of other tag files can listen for an update:
myApp.on('NewMessage', function(message) {
// Do something with the new message "message"
console.log('Message received: ' + message);
});
Maybe overkill but simple. let riot self observable
riot.observable(riot);
So you can use
riot.on('someEvent', () => {
// doing something
});
in a tag, and
riot.trigger('someEvent');
in another.
It's not good to use global variable, but use an already exists one maybe acceptable.

Categories