Aurelia: Is there any option / decorator to restrict an attribute to a specific element - aurelia

Hi I have created a custom element named as 'panel' and a custom attribute 'panel-type'. I want to restrict this 'panel-type' can be used only in 'panel' element. Is there any way to do that?
<panel value.bind='panel' panel-type='default'></panel>
--should work. But no other element can use 'panel-type'. like-
<some-other-tag panel-type='default'></some-other-tag>
--shouldn't work

Without seeing the code of your Custom Attribute - I can't tailor this to your specific needs - but it's easily possible;
In your Custom Attribute you have access to the the Element property - which is the physical element you've attached the attribute to. Therefore, you can simply use native JavaScript to get the elements tag name;
import {inject, LogManager} from "aurelia-framework";
#inject(Element)
export class panelTypeCustomAttribute {
allowedElememt = false;
constructor(Element) {
this.element = Element;
if(this.element.tagName == "PANEL") {
this.allowedElement = true;
} else {
LogManager.getLogger('testCustomAttribute').warn("The panel-type custom attribute can only be used on <panel /> elements");
}
}
foo() {
// Then you can check for the allowedElement flag in any of your methods;
if(this.allowedElement) {
// Do your thing
}
}
}

Related

ckeditor5 - custom container element - recursion error on paste

I'm trying to create a CKEditor5 custom element plugin - mainly for custom format/styles -- nested divs etc. Managed to be able to inject/format the elements, and I can type in them. But if I try to copy and paste text into a custom element I get a too much recursion error.
MyWidget plugin:
export default class MyWidgetPlugin extends Plugin {
init() {
const editor = this.editor;
editor.model.schema.register('my-widget', {
inheritAllFrom: '$root',
isLimit: true,
});
editor.conversion.elementToElement({ model: 'my-widget', view: 'my-widget' });
editor.commands.add('myWidget', new MyWidgetCommand(editor));
}
}
MyWidget command:
class MyWidgetCommand extends Command {
execute() {
const editor = this.editor;
const block = first(this.editor.model.document.selection.getSelectedBlocks());
this.editor.model.change(writer => {
const myWidget = writer.createElement('my-widget')
writer.insert ( myWidget, block, 'after');
writer.appendElement( 'paragraph', myWidget );
});
}
}
Inserting a widget injects this into the editor:
<my-widget>
<p></p>
</my-widget>
And I can type fine, but I can't paste. I'm guessing I got the schema wrong... have played around with quite a few different options.. but to no avail.
I didn't check it but I think that the issue is here:
editor.model.schema.register('my-widget', {
inheritAllFrom: '$root',
isLimit: true,
});
This schema rule says that <my-widget> will allow e.g. a <paragraph> inside it. But it doesn't say anything about where <my-widget> may be used. That's because $root is not allowed in any other element (cause it's a root :)).
I think that the following should work fine:
editor.model.schema.register('my-widget', {
inheritAllFrom: '$root',
allowIn: '$root',
isLimit: true,
});
Alternatively, a more generic solution should work too:
editor.model.schema.register('my-widget', {
inheritAllFrom: '$root',
allowWhere: '$block',
isLimit: true,
});
Still, the editor should not crash with an infinite loop, so I reported https://github.com/ckeditor/ckeditor5-engine/issues/1441.

Geb Modules showing an error

I'm working on a geb page object with a repeating set of UI elements a div container with a text input and button within.
I am attempting the following:
class MyModule extends Module{
static content = {
textInput {$("input.editTextField")}
removeInputButton {$("button.removeButton")}
}
}
class MyPage extends Page{
static content = {
myInputs { index ->
$("div.container", index).module(MyModule)
}
}
In IntelliJ the code is highlighted in MyPage on ("div.container, index) when I hover over this I see "'$' in 'geb.Page' cannot be applied to '(java.lang.String.?)'
My goal is to be able pick an iteration of the UI and to perform something like:
myInputs(0).textInput = 'foo'
myInputs(1).textInput = 'bar'
myInputs(5).removeInputButton.click()
I've referred to the documentation for Geb but by all accounts this should work. Any help would be appreciated.
This will work, it's just that your code is not type safe enough for IntelliJ to know which method you're calling. If you change your content definition to:
myInputs { int index ->
$("div.container", index).module(MyModule)
}
then the warning will disappear.

Create a new binding context for a custom attribute

What I want
<div amazingattr.bind="foo">
${$someValueFromAmazingattr}
</div>
Just like how this works:
<div repeat.for="bar of bars">
${$index}
</div>
Where I got stuck
import {customAttribute} from "aurelia-framework";
#customAttribute("amazingattr")
export class AmazingattrCustomAttribute {
bind(binding, overrideContext) {
this.binding = binding;
}
valueChanged(newValue) {
this.binding.$someValueFromAmazingattr = newValue;
}
}
While this works, the $someValueFromAmazingattr is shared outside the custom attribute's element, so this doesn't work:
<div amazingattr.bind="foo">
Foo: ${$someValueFromAmazingattr}
</div>
<div amazingattr.bind="bar">
Bar: ${$someValueFromAmazingattr}
</div>
Both of the "Foo:" and the "Bar:" show the same last modified value, so either foo or bar changes, both binding change to that value.
Why I need this?
I'm working on a value animator, so while I cannot write this (because value converters cannot work this way):
${foo | animate:500 | numberFormat: "0.0"}
I could write something like this:
<template value-animator="value:foo;duration:500">
${$animatedValue | numberFormat: "0.0"}
</template>
I imagine I need to instruct aurelia to create a new binding context for the custom attribute, but I cannot find a way to do this. I looked into the repeat.for's implementation but that is so complicated, that I could figure it out. (also differs in that is creates multiple views, which I don't need)
After many many hours of searching, I came accross aurelia's with custom element and sort of reverse engineered the solution.
Disclaimer: This works, but I don't know if this is the correct way to do it. I did test this solution within embedded views (if.bind), did include parent properties, wrote parent properties, all seem to work, however some other binding solution also seem to work.
import {
BoundViewFactory,
ViewSlot,
customAttribute,
templateController,
createOverrideContext,
inject
} from "aurelia-framework";
#customAttribute("amazingattr")
#templateController //This instructs aurelia to give us control over the template inside this element
#inject(BoundViewFactory, ViewSlot) //Get the viewFactory for the underlying view and our viewSlot
export class AmazingattrCustomAttribute {
constructor(boundViewFactory, viewSlot) {
this.boundViewFactory = boundViewFactory;
this.viewSlot = viewSlot;
}
bind(binding, overrideContext) {
const myBindingContext = {
$someValueFromAmazingattr: this.value //Initial value
};
this.overrideContext = createOverrideContext(myBindingContext, overrideContext);
//Create our view, bind it to our new binding context and add it back to the DOM by using the viewSlot.
this.view = this.boundViewFactory.create();
this.view.bind(this.overrideContext.bindingContext, overrideContext);
this.viewSlot.add(this.view);
}
unbind() {
this.view.unbind(); //Cleanup
}
valueChanged(newValue) {
//`this.overrideContext.bindingContext` is the `myBindingContext` created at bind().
this.overrideContext.bindingContext.$someValueFromAmazingattr = newValue;
}
}

Change template dynamically from view-model (Aurelia)

Is it possible to change which html-template is being used dynamically from the view-model?
E.g. based on data downloaded from a server, I'd like to choose different templates (or some other logic in the view-model)
Update
Based on the answer below suggesting getViewStrategy, here's a complete sample:
export class MultiView {
gender : string
getViewStrategy() {
if(this.gender == 'boy')
return './multi-view-blue.html'
else
return './multi-view-pink.html'
}
// when view is made visible (e.g. by using the router)
activate() {
this.gender = Math.random()>0.5 ? "boy" : "girl"
}
}
If you want to do this on a single view model implement the getViewStrategy function.
export class MyView{
getViewStrategy(){
return 'my-other-view.html';
}
}
Refer to the documentation under App Configuration and Startup, titled Configuring the View Location Convention. Here's and excerpt:
To do this, during bootstrap, import the ViewLocator and replace its convertOriginToViewUrl method with your own implementation.
It includes a code example you may follow as well.
As an alternative, you could look into the aurelia-compiler library module.
NOTE: This library will be refactored and parts of it will be included into the core. In the meantime it can still be used but please be aware of this breaking change.
It contains a function called swapView() that looks like it may do what you want. An example of it being used is in the aurelia-dialog library module. Hopefully you can glean some useful information from that and find a way to make it work.
Write a view-model that takes data from server and binding variables of class.
export class MyClass{
constructor(){
this.red = false;
this.green = false;
this.yellow = false;
this.serverValue = "";
}
activate(){
return this.bindingFunction();
}
bindingFunction(){
if(this.serverValue == 'red') { this.red = true; }
else if(this.serverValue == 'green') { this.green = true; }
else this.yellow = true;
}
}
Write the view as a whole, with show.bind, and bind those with view-model.
<template>
<div show.bind='red'> /* your elements */ </div>
<div show.bind='green'> /* your elements */ </div>
<div show.bind='yellow'> /* your elements */ </div>
</template>

Custom attribute in aurelia no working?

I am learning how Aurelia works and I am trying to get a simple custom attribute working. All it will do is change the color of a div text depending on some value changing.
I have a div which has:
high.bind="changeColor"
and in my attribute I have :
import {inject, customAttribute} from 'aurelia-framework';
#customAttribute('high')
#inject(Element)
export class High {
constructor(element) {
this.element = element;
}
valueChanged(newValue){
console.log(newValue);
if (newValue) {
this.element.classList.remove('highlight-yellow');
} else {
this.element.classList.add('highlight-blue');
}
}
In my view model I have :
import {high} from './highlightattribute'
export class Welcome{
heading = 'Welcome to the Aurelia Navigation App!';
firstName = 'John';
lastName = 'Doe';
get fullName(){
return `${this.firstName} ${this.lastName}`;
}
get changeColor(){
if (this.firstName == 'John'){
return false;
}
return true;
}
welcome(){
alert(`Welcome, ${this.fullName}!`);
}
}
When I change the firstname I do not see the valueChanged event being triggered in the high custom attribute class.
It looks like you are importing the high code in to your viewmodel rather than your view. Remove this line in your ViewModel:
import {high} from './highlightattribute'
Then and add this line to your View:
<require from="./highlightattribute"></require>
Next, in the highlightattribute.js file you are removing highlight-yellow and adding highlight-blue, so you will probably want to add and remove the same class. I did also notice that there is a missing parenthesis in your highlightattribute.js file you posted, but that was probably just missed while copying the code.
Let me know if this helps solve the problems. I have pushed a sample with your code to here: https://github.com/AshleyGrant/skeleton-navigation/tree/so-answer-20150416-01/src