I have a function that I'd like to use in several of my ViewModels. It's a filter function that I use as a param for a custom filter:
<tr repeat.for="server of servers | filter:searchTerm:filterFunc">
What is the "aurelic" way of storing functions like this? I'm very new to both js and aurelia, so an easy-to-understand "non-minified" example would make me super-grateful :-)
EDIT: For reference, I stuck the function inside my ValueConverter class since it will only be used in conjunction with it.
export class filterValueConverter {
myCustomFunc(stuf,stuf){}
toView(array, searchTerm){return this.myCustomFunc}}
You would probably need to create a value converter for an array to do this.
Html in view(s):
<div repeat.for="item of [1, null, 2] | notNullFilter">${item}</div>
Filter (src\resources\value-converters\notNullFilterValueConverter.js):
export class notNullFilterValueConverter {
toView(array) {
return array.filter(item => item !== null);
}
}
And register it as global resource in your main.js setup:
import {Aurelia} from 'aurelia-framework';
export function configure(aurelia: Aurelia) {
aurelia.use
.standardConfiguration()
.developmentLogging()
.globalResources([
"./src/resources/value-converters/notNullFilterValueConverter"
]);
aurelia.start().then(() => aurelia.setRoot());
}
You can just insert any number of value converters in the array you give to the global resources function.
Which will output
<div>1</div>
<div>2</div>
If at some stage this becomes clouded/ messy, you could move it to a feature:
export function configure(aurelia: Aurelia) {
aurelia.use
.standardConfiguration()
.developmentLogging()
.feature('resources');
aurelia.start().then(function () { return aurelia.setRoot('views/app'); });
}
Add a folder named 'resources' with a file inside index.js
export function configure(config) {
config.globalResources('./notNullFilterValueConverter', './welcomeValueConverter');
}
A 'feature' is the same as a plugin with the only difference that it is living in your source tree. It allows you to make multiple features, so there could be for example one called company-array-filters and one custom-company-elements.
Related
Background: Trying to use ckeditor5 as a replacement for my homegrown editor in a non-invasive way - meaning without changing my edited content or its class definitions. Would like to have WYSIWYG in the editor. Using django_ckeditor_5 as a base with my own ckeditor5 build that includes ckedito5-inspector and my extraPlugins and custom CSS. This works nicely.
Problem: When I load the following HTML into ClassicEditor (edited textarea.value):
<p>Text with inline image: <img class="someclass" src="/media/uploads/some.jpeg"></p>
in the editor view area, browser-inspection of the DOM shows:
...
<p>Text with an inline image:
<span class="image-inline ck-widget someclass ck-widget_with-resizer" contenteditable="false">
<img src="/media/uploads/some.jpeg">
<div class="ck ck-reset_all ck-widget__resizer ck-hidden">
<div ...></div></span></p>
...
Because the "someclass" class has been removed from and moved to the enclosing class attributes, my stylesheets are not able to size this image element as they would appear before editing.
If, within the ckeditor5 view, I edit the element using the browser inspector 'by hand' and add back class="someclass" to the image, ckeditor5 displays my page as I'd expect it with "someclass" and with the editing frame/tools also there. Switching to source-editing and back shows the class="someclass" on the and keeps it there after switching back to document editing mode.
(To get all this, I enabled the GeneralHtmlSupport plugin in the editor config with all allowed per instructions, and that seems to work fine.) I also added the following simple plugin:
export default class Extend extends Plugin {
static get pluginName() {
return 'Extend';
}
#updateSchema() {
const schema = this.editor.model.schema;
schema.extend('imageInline', {
allowAttributes: ['class']
});
}
init() {
const editor = this.editor;
this.#updateSchema();
}
}
to extend the imageInline model hoping that would make the Image plugin keep this class attribute.
This is the part where I need some direction on how to proceed - what should be added/modified in the Image Plugin or in my Extend plugin to keep the class attribute with the element while editing - basically to fulfill the WYSIWYG desire?
The following version does not rely on GeneralHtmlSupport but creates an imageClassAttribute model element and uses that to convert only the image class attribute and place it on the imageInline model view widget element.
import Plugin from '#ckeditor/ckeditor5-core/src/plugin';
export default class Extend extends Plugin {
static get pluginName() {
return 'Extend';
}
#updateSchema() {
const schema = this.editor.model.schema;
schema.register( 'imageClassAttribute', {
isBlock: false,
isInline: false,
isObject: true,
isSelectable: false,
isContent: true,
allowWhere: 'imageInline',
});
schema.extend('imageInline', {
allowAttributes: ['imageClassAttribute' ]
});
}
init() {
const editor = this.editor;
this.#updateSchema();
this.#setupConversion();
}
#setupConversion() {
const editor = this.editor;
const t = editor.t;
const conversion = editor.conversion;
conversion.for( 'upcast' )
.attributeToAttribute({
view: 'class',
model: 'imageClassAttribute'
});
conversion.for( 'dataDowncast' )
.attributeToAttribute({
model: 'imageClassAttribute',
view: 'class'
});
conversion.for ( 'editingDowncast' ).add( // Custom conversion helper
dispatcher =>
dispatcher.on( 'attribute:imageClassAttribute:imageInline', (evt, data, { writer, consumable, mapper }) => {
if ( !consumable.consume(data.item, evt.name) ) {
return;
}
const imageContainer = mapper.toViewElement(data.item);
const imageElement = imageContainer.getChild(0);
if ( data.attributeNewValue !== null ) {
writer.setAttribute('class', data.attributeNewValue, imageElement);
} else {
writer.removeAttribute('class', imageElement);
}
})
);
}
}
Well, Mr. Nose Tothegrind found two solutions after digging through ckeditor5 code, here's the first one. This extension Plugin restores all image attributes that are collected by GeneralHtmlSupport. It can be imported and added to a custom ckeditor5 build app.js file by adding config.extraPlugins = [ Extend ]; before the editor.create(...) statement.
import Plugin from '#ckeditor/ckeditor5-core/src/plugin';
import GeneralHtmlSupport from '#ckeditor/ckeditor5-html-support/src/generalhtmlsupport';
export default class Extend extends Plugin {
static get pluginName() {
return 'Extend';
}
static get requires() {
return [ GeneralHtmlSupport ];
}
init() {
const editor = this.editor;
this.#setupConversion();
}
#setupConversion() {
const editor = this.editor;
const t = editor.t;
const conversion = editor.conversion;
conversion.for ( 'editingDowncast' ).add( // Custom conversion helper
dispatcher =>
dispatcher.on( 'attribute:htmlAttributes:imageInline', (evt, data, { writer, mapper }) => {
const imageContainer = mapper.toViewElement(data.item);
const imageElement = imageContainer.getChild(0);
if ( data.attributeNewValue !== null ) {
const newValue = data.attributeNewValue;
if ( newValue.classes ) {
writer.setAttribute('class', newValue.classes.join(' '), imageElement);
}
if ( newValue.attributes ) {
for (const name of Object.keys(newValue.attributes)) {
writer.setAttribute( name, newValue.attributes[name], imageElement);
}
}
} else {
writer.removeAttribute('class', imageElement);
}
})
);
}a
}
I want to use an external JS function into a vue file.
Js file contains the method:
let calculateDiscount = {
PriceAfterDiscount(price, discountPercentage) {
const discountAmount = (discountPercentage * price) / 100;
return price - discountAmount;
}
};
export default calculateDiscount;
Vue file
<template>
<div v-for="product in product_data" :key="product.id">
<h4> ${{ Discount(product.price, product.sale) }}</h4>
</div>
</template>
<script>
import CalculateDiscount from "#/utilities/CalculateDiscount"; //js file containing the method
name: "ShowProduct",
methods: {
Discount(){
CalculateDiscount.PriceAfterDiscount();
}
}
</script>
I far I guess I can't import the function in method property in the right manner. Can anyone help me? Thanks in advance.
You need to update calculateDiscount object like:
let calculateDiscount = {
PriceAfterDiscount: (price, discountPercentage) => {
const discountAmount = (discountPercentage * price) / 100;
return price - discountAmount;
}
};
and then CalculateDiscount.PriceAfterDiscount(); should work fine.
In the template, you had called Discount function with two params like:
{{Discount(product.price,product.sale)}}
but in actual code you had passed nothing:
methods: {
Discount() {
calculateDiscount.PriceAfterDiscount();
}
}
Also, you have passed nothing to calculateDiscount.PriceAfterDiscount(). You need the pass the values from template to this and also return the result, otherwise it will never print nothing in UI:
methods: {
Discount(price, sale) {
return calculateDiscount.PriceAfterDiscount(price, sale);
}
}
JavaScript modules work as namespaces and don't need additional object literal or class to be wrapped around exports.
PriceAfterDiscount doesn't rely on calculateDiscount members and doesn't need to be defined as a part of it.
It can be:
export function PriceAfterDiscount(price, discountPercentage) {
const discountAmount = (discountPercentage * price) / 100;
return price - discountAmount;
}
};
And imported like:
import { PriceAfterDiscount } from "#/utilities/CalculateDiscount";
PriceAfterDiscount(...);
Or if it needs a namespace:
import * as calculateDiscount from "#/utilities/CalculateDiscount";
calculateDiscount.PriceAfterDiscount(...);
I'm using the vuetify-upload-button component to be able to upload files. Using the function callback fileChangedCallback that only gets the file loaded.
But, I need to have extra parameters to be able to make a logic. Is it possible to pass extra parameters to this function?
This is my code:
v-flex.listFilesUpload(v-for="(file, index) in attachments" :key="index")
p
span
| {{ index + 1 }}
.btns-upload-download
upload-btn(title="File" :fileChangedCallback="fileChanged" color="default")
import UploadButton from 'vuetify-upload-button'
export default {
[..]
methods: {
fileChanged (file) {
// Here I need my other variable file because it have the filename.
if (file) {
formData.append('filenames[]', file)
}
},
components: {
'upload-btn': UploadButton
}
}
}
I'm currently refactoring a FreeCodeCamp repo as way to learn mobx. What I'm trying to do is calculate this.store.studentCount as you can see in the StudentModal Component. Take a look here:
This is my /client/src/components/students/Modal.js
#observer
#inject('StudentModal')
export default class StudentModal extends Component {
store = new this.props.StudentModal()
renderStudentCount() {
let message
if (this.store.studentCount > 1) {
message = `${this.store.studentCount} students`
} else {
message = `${this.store.studentCount} student`
}
return <div id="student-count">{message}</div>
}
render() {
return (
<div className="AddStudentForm">
<div className="class-table-button-container">
<Button
onClick={this.open}
>
Add Student
</Button>
{this.renderStudentCount()}
</div>
.....
)
}
}
Taking a look at my models for the Modal component you can see I need to fetch a service to get the length of this but for whatever reason I cannot set the studentCount to a new value.
This is my /client/src/models/students/Modal.js
import { observable, action } from 'mobx'
import StudentsService from '../../services/StudentsService'
export default class StudentModal {
#observable open = true
#observable studentCount = 0
#action
fetchStudents() {
StudentsService.fetchStudents().then(response => {
const studentCount = response.body
this.studentCount = studentCount.length
})
}
}
You can take a look at the full source code here: https://github.com/imcodingideas/classroom-mode/tree/mobx-migration which I should remind you that this is open-source.
Am I doing this correctly? Do you have any feedback for me?
There seem to be some minor things:
Identical class names
This might lead to problems since, both your store and react component are called StudentModal
decorator order
as #Joseph suggested swap the order around your class:
#inject("StudentModal")
#observer
export default class StudentModal
State management
store = new this.props.StudentModal()
On creation of every StudentModal you seem to create a new state store. Normally te store is instantiated once (unless you really want seperate stores per modal) inside your entry point and then used later on:
import { render } from "react-dom";
import { Provider } from "mobx-react";
var stores = { StudentModal: new StudanModalStore() }
render(
<Provider {...stores}>
<StudentModal />
</Provider>,
rootEl,
);
#observer
#inject('StudentModal')
export default class StudentModal extends Component {
//used getter instead of setting once
// no longer use `new` but directly reference instance of the store.
get store (): StudentModalStore { return this.props.StudentModalas; }
}
above code is in typescript.
I am trying to use the ionRangeSlider plugin in Aurelia but am not sure how to make use of it.
https://github.com/IonDen/ion.rangeSlider/issues
I have jspm'd it into my project but how do I import it as well as call the one function that runs the plugin?
You will find the exact package names for including ion-rangesider in your package.json:
jspm": {
"dependencies": {
...
"ion-rangeslider": "npm:ion-rangeslider#^2.1.3",
"jquery": "npm:jquery#^2.2.3",
...
}
}
Then you need to create your own custom element like:
import {inject, noView} from 'aurelia-framework';
//import your dependencies
import $ from 'jquery';
import ionRangeSlider from 'ion-rangeslider';
#noView()
#inject(Element)
export class Slider {
constructor(element){
this.element = element;
}
bind(){
$(this.element).ionRangeSlider({min: 100, max: 1000, from: 550});
}
}
And where you want to use your slider you have to write:
<require from='./slider'></require>
<require from="ion-rangeslider/css/ion.rangeSlider.skinHTML5.css"></require>
<require from="ion-rangeslider/css/ion.rangeSlider.css"></require>
<slider></slider>
Normally you would put the <require from="xxx.css"></require> tags inside slider.html to ensure style encapsulation. In my example i put them where i wanted to use the slider because so i donĀ“t needed to create a slider.html.
Here is an example how to use the bootstrap popover.
I guess you should be able to do the same and calling $("#example_id").ionRangeSlider(); from within the bind function
if you imported all resources
Install ion-rangeslider first:
npm install ion-rangeslider
jspm install npm:ion-rangeslider
Create a custom attribute
import {customAttribute, bindable, inject} from 'aurelia-framework';
import {ionRangeSlider} from 'ion-rangeslider';
#customAttribute('rangeslider')
#inject(Element)
export class RangesliderCustomAttribute {
//make your own options based on requirements
options = { type: "single", min: 0, max: 100 };
constructor(element) {
this.element = element;
}
attached() {
$(this.element).ionRangeSlider(this.options).on('change', e => {
fireEvent(e.target, 'input');
});
}
detached() {
$(this.element).ionRangeSlider('destroy').off('change');
}
}
function createEvent(name) {
var event = document.createEvent('Event');
event.initEvent(name, true, true);
return event;
}
function fireEvent(element, name) {
var event = createEvent(name);
element.dispatchEvent(event);
}
import css into app.html or where you import css in your application
<require from="ion-rangeslider/css/ion.rangeSlider.css"></require>
<require from="ion-rangeslider/css/ion.rangeSlider.skinNice.css"></require>
Now you can use your attribute in input in any view
<require from="./rangeslider"></require>
<input rangeslider type="text" value.bind="yourInitialSliderValue">