Run function when element was created - javascript

I have some elements created by v-for. How can I run the function only once by keeping 'for every element creation' as a condition .
<div v-for"value in values">
<div # function(value, domElement) if value.bool===true #>
</div>

the easiest way, IMO, would be to make each of those elements a Vue Component & pass the function down as a prop.
File One
<div v-for="value in values">
<Custom-Component :propValue="value" :propFunction="functionYouNeed" />
</div>
Custom Component
<template>
<div> {{propValue.value}} </div>
</template>
<script>
export default {
props: ['propFunction', 'propValue'],
created(){
if (this.propValue.bool === true) {
this.propFunction()
}
}
}
</script>

It's not so clear what exactly you want:
<div # function(value, domElement) if value.bool===true #>
So, here's all possible solutions you want to implement.
You can bind the method using once modifier:
<div #click.once="yourMethod">
Or, if you want not to change the content then you can use v-once:
<div v-once>{{ neverChanged }}</div>
But if you just need to use the function when it was created then call the function inside created property and do not bind the method anywhere else.
created() {
if(condition) {
this.yourMethod()
}
}

Related

How would I add an "active" class to the rendered images based on object properties?

I have the following JSON file - https://ddragon.leagueoflegends.com/cdn/9.14.1/data/en_US/runesReforged.json
As you can see, there are 5 main rune categories and each category has 4 slots which contain 3 runes each ( except the first one which contains 4 runes ). My code below iterates over the 4 slots and render the images of all runes per slot. My end result looks like this:
All good so far, except that I want to to add an active class to the runes that are selected by the user. All selected runes can be found as properties inside a data property called match.
data(){
return {
match: null
}
},
methods: {
primaryPerks(){
let perksTree = Object.entries(this.$store.state.summonerRunes).find(([key,value]) => value.id === this.match.mainParticipant.stats.perkPrimaryStyle);
return perksTree[1]
}
}
It's null because the object is assigned after a GET request. In that object there are properties called perk0, perk1, perk2 and perk3 which are the ids of the runes. I have to somehow insert a check in my iteration that renders the images and add an "active" class to the rune images if their id is equal to any of the perk properties. The problem is that I'm not sure how to implement that check.
<div class="runes">
<div v-for='runes in this.primaryPerks().slots' class="perks">
<div v-for='rune in runes.runes' class="perk">
<img class='inactive' :src="'https://ddragon.leagueoflegends.com/cdn/img/' + rune.icon" alt="">
</div>
</div>
</div>
So at some point match object looks something like this?
data(){
return {
match: {
perk0: 8112,
perk1: 8113,
perk2: 8115,
perk3: 8122,
}
}
},
If so, than this is how to conditionaly attach a class.
<div class="runes">
<div v-for='runes in this.primaryPerks().slots' class="perks">
<div v-for='(rune,index) in runes.runes' class="perk">
<img :class="{inactive: Object.values(match).find(rune.id)}" :src="'https://ddragon.leagueoflegends.com/cdn/img/' + rune.icon" alt="">
</div>
</div>
</div>
I would suggest you make the method for rune checking which returns true or false and takes id as an argument.
checkRune(id) {
return Object.values(match).find(id)
}
then your class should look like :class="{inactive: checkRune(rune.id)}

accessing *ngFor last in its component in angular 6

In angular 6 I want to access *ngFor last value as I want to operation if last value is set
eg
<li [ngClass]="list.mydata==1?'replies a':'sent a'" *ngFor="let list of chatlist; let last=last;">
<span [last]="last"></span>
<img src="{{list.profile_img}}" alt="" />
<div *ngIf="list.sender_type==0">
<p>{{list.message}}{{last}}</p>
</div>
<div *ngIf="list.sender_type==1">
<p style="background-color: burlywood;">{{list.message}}</p>
</div>
</li>
I want to do is [(myvar)]=last in place of let last=last
I want to bind the last variable so, I can access it is set or not in its component.
you can create a custom directive:
import { Directive, Output, EventEmitter, Input } from '#angular/core';
#Directive({
selector: '[onCreate]'
})
export class OnCreate {
#Output() onCreate: EventEmitter<any> = new EventEmitter<any>();
constructor() {}
ngOnInit() {
this.onCreate.emit('dummy');
}
}
and then you can use it in your *ngFor to call the method in your component:
<li [ngClass]="list.mydata==1?'replies a':'sent a'" *ngFor="let list of chatlist; let last=last;">
<span (onCreate)="onCreate(last)"></span>
<img src="{{list.profile_img}}" alt="" />
<div *ngIf="list.sender_type==0">
<p>{{list.message}}{{last}}</p>
</div>
<div *ngIf="list.sender_type==1">
<p style="background-color: burlywood;">{{list.message}}</p>
</div>
</li>
then in your component:
myvar: boolean = false;
onCreate(last) {
this.myvar = last;
}
checkout this DEMO.
Angular provides certain local variables when using *ngFor, one is for example last, which will (not as you expect currently) be a boolean value, being true if it is the last item. This is meant for adding specific stylings for the example to the last element of a list.
If you want that boolean you already use it correctly, but obviously the element using it should be a component. So instead of
<span [last]="last"></span>
it should be something like
<my-component [last]="last"></my-component>
where in my-component you define
#Input last: boolean;
and thus have access to it.
for example
<li [ngClass]="list.mydata==1?'replies a':'sent a'" *ngFor="let list of chatlist; let index=i;">
</li>
now you excess last elemnt like
<anyTag *ngIf="i === chatlist.length-1"></anyTag>

Render or Use DocumentFragment in a React Component

I have a requirement in my React-based application to render dynamic forms. The form definitions are stored as JSON documents and I already have a JS library that parses the definitions and returns a DocumentFragment. This library is used in other non-React applications as well so I cannot change it.
To avoid re-writing the entire logic in my React application to parse the definitions and render the forms, I want to use the existing library.
My question is, what would be the best way to render the DocumentFragment in a React component?
Here is my DocumentFragment if I just output it to the console in my render() method.
#document-fragment
<fieldset id="metadata-form-908272" class="metadata-form-rendition hide-pages">
<div class="page-header-row">
<div class="page-header-cell">
<span>[Un-named page]</span>
<button class="page-header-button button icon">
<span class="icon icon-arrow-up-11"></span></button>
</div>
</div>
<div class="page-area" id="metadata-form-page-001-area">
<div class="question-row">
<div class="question-label-cell mandatory">I have read and understood my obligations:</div>
<div class="question-input-cell">
<div class="validation-message"></div>
<label><input type="radio" value="Yes" name="metadata-form-908272-question-1">
<span>Yes</span></label>
<label><input type="radio" value="No" name="metadata-form-908272-question-1">
<span>No</span></label>
</div>
</div>
<div class="question-row">
<div class="question-label-cell">Please state all sources for the information provided:</div>
<div class="question-input-cell">
<div class="validation-message"></div>
<div class="formatted-editor">
<div class="editor-area" contenteditable="true">
<p>​</p>
</div>
</div>
</div>
</div>
</div>
</fieldset>
Update: Security Alert!
Thanks for reminding from #JaredSmith in comment, the method provided here
really has security issue. It's not proper to apply it if the library is not from your internal.
To learn more about the issue, you could look into the link of dangerouslysetinnerhtml I referred below.
Here is indeed a tricky way to achieve your goal. By the information you provided in comment:
... I call theExternalLibrary.getFormFragment({some_data}) ...
cause that DocumentFragments only in memory, maybe as you know, we need to append the fragment to a real DOM element first, so let's just create a root element for appending:
let rootElement = document.createElement("div");
let frag = theExternalLibrary.getFormFragment({some_data});
rootElement.appendChild(frag);
Now we have a pure JavaScript elements DOM tree here. In order to convert it to React elements, here is the way which involves a method that react provides: dangerouslysetinnerhtml
You could see that this method is not encouraged to use by its scary name.
render() {
let rootElement = document.createElement("div");
let frag = theExternalLibrary.getFormFragment({some_data});
rootElement.appendChild(frag);
// rootElement.innerHTML is in string type.
return <div dangerouslySetInnerHTML={{ __html: rootElement.innerHTML }} />;
}
Live example:
I took a crack at this myself, as DOMPurify and the upcoming Sanitiser API work best when returning DocumentFragments:
function getDocumentFragment(text) {
const f = document.createDocumentFragment();
const p = document.createElement("p");
p.textContent = text;
f.appendChild(p);
return f;
}
class FragmentRenderer extends React.Component {
constructor(props) {
super(props);
this.setRef = this.setRef.bind(this);
}
setRef(ref) {
this.ref = ref;
}
componentDidMount() {
this.appendFragment();
}
componentDidUpdate(prevProps) {
if (prevProps.text != this.props.text) {
this.appendFragment();
}
}
appendFragment() {
if (!this.ref) {
return;
}
while (this.ref.firstChild) {
this.ref.removeChild(this.ref.firstChild);
}
this.ref.appendChild(getDocumentFragment(this.props.text));
}
render() {
return React.createElement("div", {
ref: this.setRef
});
}
}
ReactDOM.render(React.createElement(FragmentRenderer, {
text: "Just like this"
}), document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
So now you're not casting to a string and using dangerouslySetInnerHTML, which can in extreme cases lead to the elements you write being different to the elements in the original fragment, due to parsing issues. However, you do still need to trust whereever this fragment came from - it cannot come from a user-controlled source. DOMPurify or the Sanitizer API will be your best friends here.

How to bind to object in repeat.for with Aurelia

I am currently trying to create a custom element with a bindable property and bind to that property to the variable of a repeat.for loop. This seems like it should be a simple task but I cannot get it to work and am wondering if it perhaps has to do with the variable being an object. The code for my custom element is below:
game-card.js
import { bindable } from 'aurelia-framework';
export class GameCard {
#bindable gameData = null;
#bindable name = '';
bind() {
console.log('card-game-data: ' + JSON.stringify(this.gameData, null, 2));
console.log('name: ' + this.name);
}
}
game-card.html
<template>
<div class="card medium">
<h3 class="center-align left">${gameData.name}</h3>
<div class="right-align right">${gameData.isPublic}</div>
</div>
</template>
The view that is using the custom element is below:
<template>
<require from="./game-card"></require>
<div>
<div class="row">
<div repeat.for="game of games">
<game-card class="col s6 m4 l3" gameData.bind="game" name.bind="game.name"></game-card>
</div>
</div>
</div>
</template>
The games object looks as follows:
[{name: 'SomeName', isPublic: true}, {name: 'AnotherName', isPublic: false}]
Now, when I run the code, the console.log statements in the game-card.js bind method print out undefined for the gameData, but prints out the correct name of the game for the console.log('name: ' + this.name) statement. I can't figure out why the binding is working when I bind to a property of the game object, but not when I bind to the game object itself. Any help would be greatly appreciated, thank you so much!
You have to write game-data.bind instead of gameData.bind. From the first look, this should be the only problem

How to access functions of a controller from within another controller via scope?

I have the following problem, I want to call a function of another controller from within a controller I want to use for a guided tour (I'm using ngJoyRide for the tour). The function I want to call in the other controller is so to say a translator (LanguageController), which fetches a string from a database according to the key given as parameter. The LanguageController will, if the key is not found, return an error that the string could not be fetched from the database. In my index.html fetching the string works, but I want to use it in the overlay element of my guided tour, which does not work, but only shows the "not fetched yet"-error of the LanguageController.
My index.html looks like this:
<body>
<div class="container-fluid col-md-10 col-md-offset-1" ng-controller="LangCtrl as lc" >
<div ng-controller="UserCtrl as uc" mail='#email' firstname='#firstname'>
<div ng-controller="GuidedTourCtrl as gtc">
<div ng-joy-ride="startJoyRide" config="config" on-finish="onFinish()" on-skip="onFinish()">
...
{{lc.getTerm('system_lang_edit')}}
...
</div>
</div>
</div>
</div>
</body>
The controller I'm using for the guided Tour looks like this:
guidedTourModule.controller('GuidedTourCtrl',['$scope', function($scope) {
$scope.startJoyRide = false;
this.start = function () {
$scope.startJoyRide = true;
}
$scope.config = [
{
type: "title",
...
},
{
type: "element",
selector: "#groups",
heading: "heading",
text: " <div id='title-text' class='col-md-12'>\
<span class='main-text'>"\
+ $scope.lc.getTerm('system_navi_messages') + "\
text text text text\
</span>\
<br/>\
<br/>\
</div>",
placement: "right",
scroll: true,
attachToBody: true
}
];
...
}]);
And the output I ultimately get looks like this for the overlay element:
<div class="row">
<div id="pop-over-text" class="col-md-12">
<div id='title-text' class='col-md-12'>
<span class='main-text'>
not fetched yet: system_navi_messages
text text text text
</span>
<br/>
<br/>
</div>
</div>
</div>
...
I hope someone can see the error in my code. Thanks in advance!
Things needs clarity are,
How you defined the 'getTerm' function in your Language controller, either by using this.getTerm() or $scope.getTerm(). Since you are using alias name you will be having this.getTerm in Language controller.
Reason why you are able to access the getTerm function in your overlay element is, since this overlay element is inside the parent controller(Language Controller) and you are referencing it with alias name 'lc' while calling the getTerm function. Thats' why it is accessible.
But the string you pass as a parameter is not reachable to the parent controller. that's why the error message is rendered in the overlay HTML.
Please make a plunker of your app, so that will be helpful to answer your problem.

Categories

Resources