Kendo & Aurelia: jQuery(...).kendoPager is not a function - aurelia

I'm trying to get Kendo working in Aurelia and it is not going too easy...
The following call inside the VM attached() hook throws a "jQuery(...).kendoPager is not a function" exception in shim.min.js:1444:
jQuery("#pager").kendoPager({
dataSource: dataSource
});
I've experimented with a number of ways to define the GlobalBehavior.jQueryPlugins() setting with the following being my best attempt thus far in main.js:
import {GlobalBehavior} from 'aurelia-templating-resources';
GlobalBehavior.jQueryPlugins["kendopager"] = "kendoPager";
Unfortunately there is not much documentation about this so one is prodding in the dark a bit so any help will be appreciated.
Normal jQuery functions work fine here so the problem does appear to be related to using Kendo.
Thanks in advance

You installed dependency with JSPM, but you also need to import it in your VM class file. Put this import statement at the top of the file:
import {kendoUi} from 'kendo-ui';
After that you will be able to use in attached hook:
jQuery("#pager").kendoPager({
dataSource: dataSource
});
Just one note, it's better not to refer to DOM elements but hardcoded selectors. You would better create a reference to element in template
<div ref="pager"></div>
and then in view-model have
jQuery(this.pager).kendoPager({
dataSource: dataSource
});

Related

Property 'complete' does not exist on type 'EventTarget'

In Ionic Vue, I am trying to use ion-refresher. According to the documentation, I should end with 'event.target.complete()' but this gives me the following error: Property 'complete' does not exist on type 'EventTarget'.
What should I do, that is what the official documentation tells me. Thank you.
https://ionicframework.com/docs/api/refresher
The complete() method is a method specific to the ion-refresher component in Ionic, so it will not be available on the EventTarget interface. To fix the error, you should make sure you are referencing the ion-refresher element correctly within your Vue component's template.
In your template, ensure you are using the correct event binding for the ionRefresh event, it should be like this:
<ion-refresher slot="fixed" #ionRefresh="onRefresh($event)">
</ion-refresher>
And in your script you should be able to do this:
methods: {
onRefresh(event) {
// perform your refresh logic here
event.target.complete();
}
}
event.target is the actual ion-refresher element in the DOM, and the complete() method is a method on that element that tells the ion-refresher to stop displaying the refreshing spinner.
Make sure that you have imported the ion-refresher component in your Vue file and also in your main.ts file for your Ionic Vue app.

Vue storefront: how to override function inside helper?

I need to modify 'preConfigureProduct' function under vuestorefront/core/modules/catalog/helpers/search.ts
https://github.com/vuestorefront/vue-storefront/blob/hotfix/v1.12.3/core/modules/catalog/helpers/search.ts#L51
I need to do the customisation based on my client requirement. Thus I need to override this function in my project.
But there is no document about how to override one function from helper. I am also new in vue storefront, so I dont fully understand module extending or mixins.
Thanks in advance.
I have figured out by myself, I will leave a short answer for who is stuck as well.
Additionally, I am not really sure it is a good practise, if you know a better way, leave it here please.
From what I understood is if you need to overwrite one function from helper, you also need to 'overwrite' the function which called your helper function.
In my case, I need to overwrite function preConfigureProduct , from module catalog helper.
The function called this preConfigureProduct is one asyc function configureProducts from module catalog-next, https://github.com/vuestorefront/vue-storefront/blob/hotfix/v1.12.3/core/modules/catalog-next/store/category/deprecatedActions.ts#L22.
(I know it is deprecatedActions. It is my next job to figure out how to switch to new ones)
Thus, I extended module catalog-next, and literally copy paste configureProducts(because I dont need to modify it), but import preConfigureProduct to my new file.
import { preConfigureProduct } from './helpers/search'
my new search.ts file:
import Vue from 'vue';
import config from 'config';
export const preConfigureProduct = ({ product, populateRequestCacheTags }) => {
.....
return product;
};
That is it !
Hopefully it helps others.
If there is any mistake, welcome to point it out.

Making small components on runtime

I am having a problem working with JQuery DataTable. I had to use that plugin since I had no other choice allowed due to my project requirements.
So the problem is that, I am adding rows to DataTable and in the row there's a column with button HTML tag. Now I want to bind an on click handler to the button.
dt.Rows.Add({
column_1_data,
column_2_data,
"<button #click='itsVueTime'>MyButton</button>"
});
Here dt is the DataTable's instance. Now the problem is #click won't work. I understand that its not being rendered by Vue thats why its not working.
Is there a way to bind click event in this condition?
Without knowing more context, I would recommend this way of doing it
In your component with the method you want to use, you can expose the component like this. (I use mounted, but you can use other lifecycle methods too like created)
mounted() {
window.app = this;
}
then you can use
<button onclick="app.holler()">Say Hello</button>
you can also expose just the function you want to use like so
mounted() {
window.itsVueTime = this.itsVueTime;
}

Dynamically instantiating a component in Vue.js

Following this tutorial, I'm trying to programmatically create instances of a component on my page.
The main snippet is this:
import Button from 'Button.vue'
import Vue from 'vue'
var ComponentClass = Vue.extend(Button)
var instance = new ComponentClass()
instance.$mount()
this.$refs.container.appendChild(instance.$el)
However I get two errors:
The component I'm trying to instantiate contains references to the store, and these don't work: "TypeError: Cannot read property 'state' of undefined".
For the last line of the snippet (this.$refs.container.appendChild(instance.$el)) I get this error: "Uncaught TypeError: Cannot read property 'container' of undefined"
I'm really not sure how to troubleshoot this, if anyone strong in Vue.js could give me some hint as to why I'm getting these errors and to solve them that would be terrific.
1) Since you're manually instantiating that component and it doesn't belong to your main app's component tree, the store won't be automatically injected into it from your root component. You'll have to manually provide the store to the constructor when you instantiate the component ..
import ProjectRow from "./ProjectRow.vue";
import Vue from "vue";
import store from "../store";
let ProjectRowClass = Vue.extend(ProjectRow);
let ProjectRowInstance = new ProjectRowClass({ store });
2) In a Vue Single File Component (SFC), outside of the default export this doesn't refer to the Vue instance, so you don't have access to $refs or any other Vue instance property/method. To gain access to the Vue instance you'll need to move this line this.$refs.container.appendChild(instance.$el) somewhere inside the default export, for example in the mounted hook or inside one of your methods.
See this CodeSandbox for an example of how you may go about this.
This is another way to instantiate a component in Vue.js, you can use two different root elements.
// Instantiate you main app
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
//
// Then instantiate your component dynamically
//
// Create a component or import it.
const Hello = {
props: ['text'],
template: '<div class="hello">{{ text }}</div>',
};
// Create a componentClass by Vue.
const HelloCtor = Vue.extend(Hello);
// Use componentClass to instantiate your component.
const vm = new HelloCtor({
propsData: {
text: 'HI :)'
}
})
// then mount it to an element.
.$mount('#mount');
It works by assigning "this" to the property "parent". By setting the parent you also have access to the $store in the new instance. (Provided that "this" is another Vue instance/Component and already has access to the store, of course)
new (Vue.extend(YourNewComponent))({
parent: this,
propsData: {
whatever: 'some value',
},
}).$mount(el.querySelector('.some-id'))
If you don't need the reference to the parent, you can just leave "parent: this," out.
Important note: When mounting many (like 500+) items on the page this way you will get a huge performance hit. It is better to only give the new Component the necessary stuff via props instead of giving it the entire "this" object.
I went down this path, following all the examples above, and even this one: https://css-tricks.com/creating-vue-js-component-instances-programmatically/
While I got far, and it works (I made a lot of components this way), at least for my case, it came with drawbacks. For example I'm using Vuetify at the same time, and the dynamically added components didn't belong to the outer form, which meant that while local (per component) validation worked, the form didn't receive the overall status. Another thing that did not work was to disable the form. With more work, passing the form as parent property, some of that got working, but what about removing components. That didn't go well. While they were invisible, they were not really removed (memory leak).
So I changed to use render functions. It is actually much easier, well documented (both Vue 2 and Vue 3), and everything just works. I also had good help from this project: https://koumoul-dev.github.io/vuetify-jsonschema-form/latest/
Basically, to add a function dynamically, just implement the render() function instead of using a template. Works a bit like React. You can implement any logic in here to choose the tag, the options, everything. Just return that, and Vue will build the shadow-DOM and keep the real DOM up to date.
The methods in here seems to manipulate the DOM directly, which I'm glad I no longer have to do.

Is constant polling the way Aurelia's change detection is working?

I got div with if.bind working as was suggested in this question: Using literal JavaScript values in an Aurelia view. But then I noticed that showTemplate on viewModel is being constantly checked by framework, about 10 times per second. The stack is following:
execute._prototypeProperties.visible.get (welcome.js:29)
getValue (dirty-checking.js:93)
isDirty (dirty-checking.js:127)
check (dirty-checking.js:63)
(anonymous function) (dirty-checking.js:49)
Is it supposed to be like this? Seems to be not very resource-friendly.
Best regards, Eugene.
Aurelia uses Object.observe for simple Javascript properties. If showTemplate is a function or a getter/setter, then Aurelia currently reverts to dirty checking. This can be removed by declaring the dependencies of the function. This is outlined here: https://github.com/aurelia/binding/pull/41
I have created a version of the Aurelia Skeleton project that implements this: https://github.com/ashleygrant/skeleton-navigation/tree/declare_dependencies
To implement this, you must switch to using the aurelia-main attribute. Add a main.js file:
import {LogManager} from 'aurelia-framework';
import {ConsoleAppender} from 'aurelia-logging-console';
import {ComputedObservationAdapter, ObjectObservationAdapter} from 'aurelia-framework';
LogManager.addAppender(new ConsoleAppender());
LogManager.setLevel(LogManager.levels.debug);
export function configure(aurelia) {
aurelia.use
.defaultBindingLanguage()
.defaultResources()
.router()
.eventAggregator();
aurelia.container
.registerSingleton(ObjectObservationAdapter, ComputedObservationAdapter);
aurelia.start().then(a => a.setRoot('app', document.body));
}
Then, in welcome.js, add the following import statement:
import {declarePropertyDependencies} from 'aurelia-framework';
and then outside the Welcome class, add the following method call:
declarePropertyDependencies(Welcome, 'fullName', ['firstName', 'lastName']);