I created a SharePoint Framework Webpart.
I call a webservice that returns some data from sharepoint (terms from termstore), and for each term i generate an html that display the term. On the onclick of each term i want to call a typescript function, passing the term as parameters, to get its children terms.
This code below create the following wrong behaviour: when the webpart is displayed, it automatically calls the function this.readIterm when I didnt even click on it !
Am I missing something or by design doing it the wrong way ? I tried to replace onclick="${this.readIterm(term)}" by onclick="readIterm($(term))" but it does nothing.
Code below
terms.forEach(term => {
htmlContent +=
`<div class="w3-card w3-third w3-margin-bottom" style="" onclick="${this.readIterm(term)}">
<header class="w3-container w3-blue">
<h1>Header</h1>
</header>
<div class="w3-container">
<span>${term.name}</p>
</div>
<footer class="w3-container w3-blue">
<h5>Footer</h5>
</footer>
</div>`
This html is then added to this.domElement.innerHTML, displaying it in the webpart.
public readIterm(myTerm: ITerm) {
alert("readIterm is clicked");
}
Thank you in advance for your help and guidance if I do not follow best practice !
Jeff
There is one more way to attach event listener.
Refer below code sample. This works perfectly for me:
(Note: This is a basic idea. You can refer this code and make changes to yours accordingly.)
public render(): void{
this.domElement.innerHTML = ` <div class="form-group">
<button class="btn btn-success" id="btnReadAllItems">
<span class="ms-Button-label">Read All Items</span>
</button>
<button class="btn btn-success" id="btnReadItemById">
<span class="ms-Button-label">Read Item By Id</span>
</button>
</div>`;
this._setButtonEventHandlers();
}
private _setButtonEventHandlers(): void {
const webPart: SpfxCrudWebPart = this;
this.domElement.querySelector('#btnReadAllItems').addEventListener('click', () => {
this._GetListItemsNF();
});
}
private _GetListItemsNF(): void {
//
//
//
}
Another way is as below. (But you will have to make some changes according to your code)
public render(): void{
htmlContent +=
`<div class="w3-card w3-third w3-margin-bottom" id="test">
<header class="w3-container w3-blue">
<h1>Header</h1>
</header>
<div class="w3-container">
<span>${term.name}</p>
</div>
<footer class="w3-container w3-blue">
<h5>Footer</h5>
</footer>
</div>`
}
this._setButtonEventHandlers();
this.myTerm = term; // Here we are assigning value to the `myTerm` variable. And `myTerm` is a public property, So value of `myTerm` will be accessible using `this.myTerm`
}
public myTerm: ITerm; // here we are declaring `myTerm` variable
public readIterm():void{
// here you can access the cariable `myTerm` using `this.myTerm`.
// alert(this.myTerm);
// alert("readIterm is clicked");
}
private _setButtonEventHandlers(): void {
this.domElement.querySelector('#test').addEventListener('click', () => { this.readIterm(); });
}
Rather onClick instead of onclick. Then check to make sure that you get into your "readIterm" method by calling the console (console.log).
Related
I have collection of permission_types which i can access in my view. I am looping the permission_types to show each one in check box. all the checkboxes are binded to permission_resource, which is defined as a public property array. I want all the checkboxes to be preselected for which i used checked attribute but it won't work, as soon as i remove the wire:model attribute from input all the checkboxes are preselected. which narrows down the problem to wire:model binding.
What i am trying to achieve:
All i want to do is preselect the checkboxes binded to public property $permission_resource. Can anyone please help me out. I really cannot figure out what am i doing wrong here. Appreciate any effort to solve this problem in advance.
Relevent Component Code:
public $permission_types;
public $permission_resource = [];
public function render()
{
$this->permission_types = PermissionType::all();
// dd($this->permission_types->toArray());
return view('livewire.permissions.create-permission-component')
->extends('layouts.app')
->section('content');
}
Relevant View Code:
<div class="row">
#foreach($this->permission_types as $permissionType)
<div class="col-md-3">
<input wire:model="permission_resource" type="checkbox" class="filled-in chk-col-green form-control" id="{{$permissionType['name']}}" value="{{ $permissionType['name'] }}" checked />
<label for="{{ $permissionType['name'] }}" class="p-r-30">{{ ucfirst($permissionType['name']) }} Resource</label>
</div>
#endforeach
</div>
Permission Types Dump
What i have tried so far:
so far i have tried following but none of it worked for me.
1: defining public $permission_resource; instead of public $permission_resource = [];
2: wire:model="permission_resource.{{$permissionType["id"]}}"
3: wire:model="permission_resource.{{$permissionType["name"]}}"
4: wire:model.defer="permission_resource.{{$permissionType["id"]}}"
5: wire:model.defer="permission_resource.{{$permissionType["name"]}}"
6: name="permission_resource.{{$permissionType["id"]}}"
You are binding all the inputs to the same (array) variable, what you want to do is bind each to an element in that array, not to the array itself.
So you would need to prepopulate that array, and then bind each input using $loop->index.
Not sure if you have a good reason for populating permission_types in the render method, better in mount if it is not highly dynamic (likely to change from render to render).
Populating permission_resource in mount might look like this:
public function mount() {
$this->permission_types = PermissionType::all();
$this->permission_resource = $this->permission_types->pluck('name');
}
Then in the blade, bind to elements of the array, and don't set checked yourself, wire:model will always override this anyway, checking your checkbox if the thing you have bound to is truthy, and unchecking if bound var is falsey (this is why everything is unchecked for you with the wire:model binding them to the array, because empty($array) == false, if you just fix the casing of permission_type in the blade you will find the checkbox all come on when you check one, because they are all bound to the same thing).
<input wire:model="permission_resource.{{$loop->index}}" type="checkbox" class="filled-in chk-col-green form-control" id="{{$permission_type['name']}}" value="{{$permission_type['name']}}" />
PS: That will at least get to what you wanted, depending on what you are ultimately trying to do with permission_resource, it may not be a very good/useful representation for the purpose.
try this
public $permission = [];
protected $rules = [
...
'permission' => 'required|array',
];
public function create()
{
if ($this->validate()) {
$data = [
'name' => $this->name,
....
];
$model = Model::create($data);
if (!empty($this->permission)) {
$permissionId = PermissionType::whereIn('id', $this->permission)->pluck('id');
$model->permission()->attach($permissionId); // add name relation instead 'permission' , of create 'attach' of update 'sync'
}
}
}
public function render()
{
return view('livewire.permissions.create-permission-component', [
'permissions' => PermissionType::select('id', 'name')->get()
])
->extends('layouts.app')
->section('content');
}
in View
<div class="row">
#foreach($permissions as $permission)
<div class="col-md-3">
<input wire:model.defer="permission" wire:key="permission{{$permission->id}}" type="checkbox" value="{{$permission->id}}" class="filled-in chk-col-green form-control" />
<label for="{{ $permission->name }}" class="p-r-30">{{ ucfirst($permission->name) }} Resource</label>
</div>
#endforeach
</div>
I am fetching some data using Apollo inside of Nuxt. Somehow, when navigating to that page I get an error of
Cannot read property 'image' of undefined
When I refresh the page, everything works as expected.
I have a found a few threads of people having similar issues but no solution seems to work for me :/
This is my template file right now:
/products/_slug.vue
<template>
<section class="container">
<div class="top">
<img :src="product.image.url"/>
<h1>{{ product.name }}</h1>
</div>
</section>
</template>
<script>
import gql from 'graphql-tag'
export default {
apollo: {
product: {
query: gql`
query Product($slug: String!) {
product(filter: { slug: { eq: $slug } }) {
slug
name
image {
url
}
}
}
`,
prefetch({ route }) {
return {
slug: route.params.slug
}
},
variables() {
return {
slug: this.$route.params.slug
}
}
}
}
}
</script>
Basically the $apolloData stays empty unless I refresh the page. Any ideas would be much appreciated
EDIT
Got one step closer (I think). Before, everything (image.url and name) would be undefined when navigating to the page for the first time.
I added:
data() {
return {
product: []
};
}
at the top of my export and now at least the name is always defined so if I remove the image, everything works as expected. Just the image.url keeps being undefined.
One thing I noticed (not sure how relevant) is that this issue only occurs using the , if I use a normal a tag it works but of course takes away the vue magic.
EDIT-2
So somehow if I downgrade Nuxt to version 1.0.0 everything works fine
I stumbled on this issue as well, and found it hidden in the Vue Apollo documents.
Although quite similar to the OP's reply, it appears the official way is to use the "$loadingKey" property.
It's quite confusing in the documents because there are so many things going on.
https://vue-apollo.netlify.com/guide/apollo/queries.html#loading-state
<template>
<main
v-if="!loading"
class="my-8 mb-4"
>
<div class="w-3/4 mx-auto mb-16">
<h2 class="mx-auto text-4xl text-center heading-underline">
{{ page.title }}
</h2>
<div
class="content"
v-html="page.content.html"
></div>
</div>
</main>
</template>
<script>
import { page } from "~/graphql/page";
export default {
name: 'AboutPage',
data: () => ({
loading: 0
}),
apollo: {
$loadingKey: 'loading',
page: {
query: page,
variables: {
slug: "about"
}
},
}
}
</script>
If you need to use a reactive property within vue such as a slug, you can do so with the following.
<template>
<main
v-if="!loading"
class="my-8 mb-4"
>
<div class="w-3/4 mx-auto mb-16">
<h2 class="mx-auto text-4xl text-center heading-underline">
{{ page.title }}
</h2>
<div
class="content"
v-html="page.content.html"
></div>
</div>
</main>
</template>
<script>
import { page } from "~/graphql/page";
export default {
name: 'AboutPage',
data: () => ({
loading: 0
}),
apollo: {
$loadingKey: 'loading',
page: {
query: page,
variables() {
return {
slug: this.$route.params.slug
}
}
},
}
}
</script>
I think it's only a problem of timing on page load.
You should either iterate on products, if you have more than one, or have a v-if="product != null" on a product container, that will render only once the data is fetched from GraphQL.
In that way you'll use the object in your HTML only when it's really fetched and avoid reading properties from undefined.
To fix this, you add v-if="!$apollo.loading" to the HTML container in which you're taying to use a reactive prop.
I have a component in Angular with this HTML:
<div class="row">
<label for="tipo" class="column">Tipo: </label>
<div *ngFor="let tipo of tipos" class="column">
<input type="radio" name="tipo" [value]="tipo.id"
[(ngModel)]="movimiento.tipo" (ngModelChange)="alCambiarTipo()">
<span class="{{tipo.texto | lowercase}}">{{ tipo.texto }}</span>
</div>
</div>
It has two radio buttons, and on change it triggers a function in my component. In my test I want to check the second radio button to test that my function is called. I've tried this code but It's not working:
it('should call alCambiarTipo on radio button change', () => {
spyOn(component, 'alCambiarTipo').and.callThrough();
let options: DebugElement[] = fixture.debugElement.queryAll(By.css('input[type="radio"]'));
let secondOption: HTMLInputElement = options[1].nativeElement;
secondOption.checked = true;
expect(component.alCambiarTipo).toHaveBeenCalled();
});
I've also tried using .click() on the input and it's also not working. What can I do to trigger my function? Thanks.
PS: I've also tried to change the model adding component.movimiento.tipo = 1; and calling fixture.detectChanges() and it's also not working.
The answer from #maxisam is correct, additionally you could use:
inputElement.nativeElement.dispatchEvent(new Event('change'));
If you want more compact form :)
Because you use a wrong event. It should be change event.
options[1].triggerEventHandler('change', { target: options[1].nativeElement });
You don't even need to set check
I saw an example of linking directly to a form element using JsViews, which I found to be preferable to encapsulating the whole form in a template. Here is a jsfiddle example of what I'm trying to do, which partially works:
http://jsfiddle.net/30jpdnkt/
var app = {
selections: {
things: [
{ Name: "thingName1", Value: "thingValue1" },
{ Name: "thingName2", Value: "thingValue2" },
{ Name: "thingName3", Value: "thingValue3" }
]
},
formData: {
selectedThing: "thingValue1",
}
};
//how do I reference this template in-line, outside of another wrapping template?
$.templates({
theTmpl: "#theTmpl"
});
$("#content").link(true, app);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="http://www.jsviews.com/download/jsviews.js"></script>
<script id="theTmpl" type="text/x-jsrender">
<select id="thingChoice" data-link="formData.selectedThing">
<option value="-">Please select</option>
{^{for selections.things}}
<option data-link="value{:Value} {:Name} selected{:~selected === Value}"></option>
{{/for}}
</select>
</script>
<div id="content">
<!--this part works-->
<input data-link="formData.selectedThing trigger=true"/>
<!--this part does not display-->
<span {{for data tmpl="theTmpl"/}}></span>
</div>
The data-linked INPUT tag is correctly bound to the object, but I cannot find a working example of how to reference a compiled template in-line without encapsulating the entire form in another template. That it's possible to use data-link syntax outside of a template gives hope that it may be possible with correct syntax.
Is this possible?
Yes it is possible - it is what I call top-level data-linking. There will be new documentation topics on this coming very soon, but meantime you have this sample:
http://www.jsviews.com/#samples/editable/toplevel-for
And your jsfiddle - which I updated to make it work fully: http://jsfiddle.net/30jpdnkt/1/
<div id="content">
<input data-link="formData.selectedThing trigger=true"/>
<span data-link='{for tmpl="theTmpl"}'></span>
</div>
I am trying to display the details of items in a list. This should be done by lazy loading the template (DOM for the details), because the template is very large and i've got many items in the list so a ng-show with ng-include is not working, since it is compiled into the DOM and makes the performance very bad.
After experimenting I figured out a solution, only working with a inline template. I am using a click handler to render the HTML with the detail-view directive to the DOM.
HTML
<div ng-controller="Ctrl">
{{item.name}} <button show-on-click item="item">Show Details</button>
<div class="detailView"></div>
<div ng-include="'include.html'"></div>
</div>
<!-- detailView Template -->
<script type="text/ng-template" id="detailView.html">
<p>With external template: <span>{{details.description}}</span></p>
</script>
Show On Click Directive
myApp.directive("showOnClick", ['$compile', '$parse', function($compile, $parse) {
return {
restrict: 'A',
scope: {
item: "=item"
},
link: function (scope, element, attrs) {
// Bind the click handler
element.bind('click', function() {
// Parse the item
var item = $parse(attrs.item)(scope);
// Find the element to include the details
var next = $(element).next('div.detailView');
// Include and Compile new html with directiv
next.replaceWith($compile('<detail-view details="item"></detail-view>')(scope));
});
}
};
}]);
Detail View Directive:
myApp.directive("detailView", ['$parse', '$templateCache', '$http', function($parse, $templateCache, $http) {
return {
restrict: 'E',
replace: true,
templateUrl: 'detailView.html', // this is not working
// template: "<div>With template in directive: <span>{{details.description}}</span></div>", // uncomment this line to make it work
link: function (scope, element, attrs) {
var item = $parse(attrs.details)(scope);
scope.$apply(function() {
scope.details = item.details;
});
}
};
}]);
Here is the full example on
Plunker
Is there a way to improve my solution, or what am I missing to load the external template?
Thanks beforehand!
You can also look at ng-if directive in Angular version 1.1.5 . ng-if would only render the html if condition is true. So this becomes
<div ng-controller="Ctrl">
{{item.name}} <button ng-if="showDetails" item="item" ng-click='showDetails=true'>Show Details</button>
<div class="detailView"></div>
<div ng-include="'include.html'"></div>
</div>
By just using ng-include:
<div ng-controller="Ctrl" ng-init="detailsViewTemplateSource='';">
{{item.name}}
<button ng-click="detailsViewTemplateSource = 'detailView.html'">
Show Details
</button>
<div ng-include="detailsViewTemplateSource"></div>
</div>
<!-- detailView Template -->
<script type="text/ng-template" id="detailView.html">
<p>With external template: <span>{{details.description}}</span></p>
</script>