Custom attribute in aurelia no working? - aurelia

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

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.

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

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
}
}
}

Aurelia UI Virtualization - Rebinding

I'm playing around with the cool Aurelia UI-Virtualization plugin (https://github.com/aurelia/ui-virtualization) to provide a user with a list of search results.
If they do a new search, I want to replace the current results with the new ones. I would think you just need to set the array to the new results, but that creates some weird behavior, kind of like the list is "remembering" it's old contents.
In my case, when you click on one of the search results, a separate panel shows details about that search result. But after a rebind, it shows info about the old result still.
Thanks!
Aaron
i managed to solve a similar problem using signals:
http://aurelia.io/hub.html#/doc/article/aurelia/binding/latest/binding-binding-behaviors/5
search.js:
import {inject} from 'aurelia-framework'
import {BindingSignaler} from 'aurelia-templating-resources'
export class Search
{
static inject() { return [BindingSignaler] }
constructor(signaler)
{
this.signaler = signaler
}
search()
{
// do your thing
this.searchresults = [ /* searchresults here */ ]
this.signaler.signal('update-results')
}
}
search.html
<template>
<div repeat.for="item in searchresults & signal:'update-results'">
${ item }
</div>
</template>

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>