I am developing a custom attribute on aurelia to let user select from a list while typing in a textarea. For example, the usage will be something like this:
<textarea value.bind="description" placeholder="your description here" auto-complete></textarea>
and as you probably noticed, the auto-complete is the attribute. Now when I want to show the hints, I want to do this in a custom-element to keep it simple. So the attached method of the attribute will be something like this:
attached() {
this.containerElement = document.createElement('div');
this.containerElement.style.position = 'relative';
const ce = document.createElement('autocomplete-menu');
this.containerElement.appendChild(ce);
const ceView = this.templatingEngine.enhance(ce);
ceView.attached();
const currentParrent = this.element.parentElement;
currentParrent.replaceChild(this.containerElement, this.element);
this.containerElement.appendChild(this.element);
}
now it opens and shows the hint area successfully. The screen-shot:
The problem gets started when I want to communicate to the generated element from the attribute view-model. For example, I want to send a data to its view-model or bind some object to a bindable property of that. For this issue I have found these solutions:
https://discourse.aurelia.io/t/dynamically-add-custom-attribute-to-element/1400/6
https://ilikekillnerds.com/2016/01/enhancing-at-will-using-aurelias-templating-engine-enhance-api/
and also read the last part of this:
https://aurelia.io/docs/binding/how-it-works#abstract-syntax-tree
and figured out that, I have to introduce an object for the element's view-model as its bindingContext or overrideContext. So if I am right, I have tested the solutions below:
this.containerElement.appendChild(ce);
let vm = { test: 1 }
const ceView = this.templatingEngine.enhance({ element: ce, bindingContext: vm });
ceView.addBinding(vm);
ceView.attached();
and
this.containerElement.appendChild(ce);
let vm = { test: 1 }
const ceView = this.templatingEngine.enhance(ce);
ceView.bind(vm);
ceView.attached();
console.log(ceView);
but on the attached lifecycle-hook of the element, I have logged the view-model and noticed that the bindingContext properties are not present on this.
Now there are two questions:
What's the problem with the above solutions and how can I send this kind of data to an enhanced element?
Is there a way to do this with the known method of bindables? I mean defining a bindable property on the element view-model and bind to it after the enhance method is done. Instead of working with bindingContext and overrideContext?
Fortunately the problem has been solved. The solution was not that much complex but 1. it is very very useful for me (as I will describe next) 2. the lack of aurelia documentation made this simple issue hard to solve.
The problem was about I was misunderstanding the meanings of bindingContext and container. I was thinking that bindingContext will refer to view-model of the child element and I must point its container to the parent context (which is the attribute). But I found that I should point the bindingContext to the context of attribute. I still don't know enough about those two meanings but the solution is as simple and beautiful as the following sample:
this.containerElement = document.createElement('div');
this.containerElement.style.position = 'relative';
this.containerElement.style.display = 'flex';
this.containerElement.style.flexDirection = 'row-reverse';
this.ce = document.createElement('autocomplete-menu');
this.ce.setAttribute('filter.bind', 'filter');
this.ce.setAttribute('show.bind', 'showMentionPicker');
this.ce.setAttribute('parent-height', '${element.clientHeight}px');
this.ce.setAttribute('view-model.ref', 'mentionPickerViewModel');
this.ce.setAttribute('on-select.call', 'complete(mentionPickerViewModel.getRemainingOfHint())');
const ceView = this.templatingEngine.enhance({ element: this.ce, container: this.container });
ceView.bind(this);
ceView.attached();
this.containerElement.appendChild(this.ce);
const currentParrent = this.element.parentElement;
currentParrent.replaceChild(this.containerElement, this.element);
this.containerElement.appendChild(this.element);
and the bindings are referring to the attribute's this context which has the following properties:
filter = '';
showMentionPicker = false;
mentionPickerViewModel;
If the sample is not enough for your problem ask me for more information.
Related
I've created a custom component based on Image component. I want to teach it to respond to change of binded variable. E.g. if main class has a variable balance, I want the component to change image in case balance = 100 one image, in case balance = 50 to another.
Can somebody help to understand how to do that
I am using flex3 so don't see propertyWatcner there. Also I am using component2 from main mxml file this way
<MyComp:MyIcon left="15" top="20" width="60" height="60"
id="tower" price="100" accountState="{accountMoney}"
click="drawBuildingShadow(event)" />
And inside component MyIcon I want to be able to react changes of binded accountState variable.
Without any code to go by (please include a sample of both components if you can), there are a number of ways you could approach this.
You could add a ChangeWatcher which is bound to the variable in question, and invokes a method when the property changes. This method could change the image, based on whatever conditions apply to the property.
It would look something like this:
Component 1:
[Bindable]
public var yourVariable:Number;
Component 2:
private var propertyWatcher:ChangeWatcher;
//In some initialization method -- this is JUST an example method for how to create the property watcher
public function init(): void {
...
propertyWatcher = ChangeWatcher.watch(component1, "yourVariable", onVariableUpdate);
}
//Define this as a new method to handle when the property changes
private function onVariableUpdate(event:PropertyChangeEvent):void {
if(event.newValue == 50) {
yourImage.source = newSource;
}
else if(event.newValue == 100) {
yourImage.source = otherSource;
}
}
Obviously, this is very truncated and shorthand, but hopefully it will help get you started.
Edit: ChangeWatchers do exist in Flex 3, but it sounds like you should go in a different direction. Since the code snippet you posted is a bit small, I'm going to make a few assumptions on how you might do this :)
As alxx mentioned in his comment, you can change the property accountState in your component from an actual property, to a setter/getter. This will allow you to do more extensive processing when accountState gets updated.
It should look something like this:
MyComp:
//Inside your script tag:
private var _accountState:Number;
[Bindable]
public function get accountState():Number {
return _accountState;
}
public function set accountState(state:Number):void {
_accountState = state;
switch(state) {
case 50:
yourIcon.source = "blahblahblah";
break;
case 100:
yourIcon.source = "blahblahblah2";
break;
//And so on
}
}
This won't change the code you posted: it should still work as you've written it. I haven't tested this, so it's possible I'm missing something, but hopefully this will help :)
So, in my example below, "InputDate'" is an input type=text, "DateColumn" is a TD within a table with a class of "DateColumn".
Read the value of a discreet texbox:
var inputVal = $('#InputDate').val();
Read the value of a div within a table....
This works:
$('#theTable .DateColumn').each(function() {
var rowDate = Date.parse($(this)[0].innerHTML);
});
This doesn't:
$('#theTable .DateColumn').each(function() {
var rowDate = Date.parse($(this)[0].innerHTML());
});
The difference is the "()" after innerHTML. This behavior seems syntactically inconsistent between how you read a value from a textbox and how you read it from a div. I'm ok with sometimes, depending on the type of control, having to read .val vs .innerHTML vs.whateverElseDependingOnTheTypeOfControl...but this example leads me to believe I now must also memorize whether I need trailing brackets or not on each property/method.
So for a person like me who is relatively new to jQuery/Javascript....I seem to have figured out this particular anomaly, in this instance, but is there a convention I am missing out on, or does a person have to literally have to memorize whether each method does or does not need brackets?
innerHTML is javascript, and is a property of an element. If you'd like to stick with the jQuery version of doing things, use html():
$('#theTable .DateColumn').each(function() {
var rowDate = Date.parse($(this).html() );
});
edit: a bit more clarification about your concerns. jQuery is pretty consistent in it's syntax. Basically, most of the methods you find allow read/write access by adjusting the parameters passed to the method.
var css = $('#element').css('color'); // read the color of the element
$('#element').css('color', 'red'); // set the color to "red"
var contents = $('#element').html(); // grab the innerHTML of the element
$('#element').html('Hello World'); // set the innerHTML of this element
.innerHTML is a property of the element not a method.
Property reference Example: object.MyProperty
Method Example: object.SomeFunction();
I have a quite large Flex application with a large set of views and I ceratain views I'd like to add shortcuts.
And i'm looking for something like:
<mx:Vbox>
<foo:Shortcut keys="ctrl+s" action="{bar();}"/>
....
</mx:VBox>
Is there any framwork or component already done that does something like this? I guess it should be too difficult to build? When building this I only want the shortcuts to be active when the view is visible. How do I detect this? What base class is best to inherit from when working with non visual components?
I don't know of any framework component that does that already, but the examples above should get you started if you try to build your own.
There's no need to inherit from any component for a non-visual component like the one you've described here (your "foo" class needs no parents.) There's nothing in the Flex framework you need to inherit from for this.
However you architect it, your foo class is going to have to take in and parse keyboard codes to listen for and accept one or more methods to call. All you have to do is figure out when to add and remove the event listeners that will call the passed-in methods.
To handle turning your keyboard events on and off based on visibility, just have your foo component bind to the "visible" property of it's parent and add/remove event listeners accordingly.
You might also consider having the listeners added when the component that foo is nested in is on the display list rather than just visible. To do this, simply added and remove your event listeners in one of the component lifecycle methods - probably commitProperties is the most appropriate.
I don't think this solution answer your question directly but anyway, to help solve your problem here is an example.
For instance, I've extended the TextArea component like so. This is the best I can do so far, it can definitely be improved upon. Like, I don't know how to make the cursor go to the end after the next shortcut is pressed.
public class TextArea extends mx.controls.TextArea
{
// the keysmap is an example dictionary holding keycodes
private var keysmap:*={
112 = "some text for F1"
,113 = "the text for F2!"
//etc, etc
}
public var handleKeyDown:Boolean =false;
public function TextArea(){
if(handleKeyDown ==true){
this.addEventListener(KeyboardEvent.KEY_DOWN,this.keydownHandler);
}
}
public function keydownHandler(e:KeyboardEvent):void{
if(e.keyCode >= 112 && e.keyCode <= 123){
e.currentTarget["text"] += String(keysmap[e.keyCode]) +" ";
}//focusManager.setFocus(this);
}
}
I can't give you a solution using MXML, however my first thought would involve a singleton static class with a Dictionary that contains a list of objects as its keys and dynamically created dictionaries as the value pairing that contain keys denoting the desired key press with a function reference as the value.
So, say you had a Sprite and you wanted to capture ctrl+s for save when focus is on that object, I would get the instance of that Singleton, and call a function such as registerKeyBinding passing in the Sprite, the keyCode you want, and your pre-defined callback:
private var registeredObjects:Dictionary = new Dictionary(true);
public function registerKeyBinding(targetObject:Object, keyCode:int, callback:Function) {
if (registeredObjects[targetObject]) {
Dictionary(registeredObjects[targetObject])[keyCode] = callback;
}
else {
registeredObjects[targetObject] = new Dictionary();
Dictionary(registeredObjects[targetObject])[keyCode] = callback;
targetObject.addEventListener(KeyboardEvent.KEY_DOWN, keyDownListener);
}
}
private function keyDownListener(e:KeyboardEvent):void {
if (e.ctrlKey == true) {
//calls the function if that key exists.
Dictionary(registeredObjects[e.target])[e.keyCode];
}
}
Can't say I've tested this, but it was just the first thing that popped into my head. You could then setup functions to deregister and delete keys from the dictionaries, check states of the objects in addition to the keyCodes, remove old listeners, and delete entire dictionaries when there is no longer a need for them. Hopefully this is at least a tiny bit helpful.
I'm using a tree control that I want to customize. The data items in the tree's dataProvider have a property name that should be used for labeling the node, and a property type that should be used to select one of several embedded images for use as an icon. The simplest way to do this is by using the labelField and iconFunction properties.
However, I wanted to get started with item renderers and open the door for adding more complex customization later, so I tried making my own item renderer. I extended the TreeItemRenderer class as follows and used it in my tree control:
class DirectoryItemRenderer extends TreeItemRenderer
{
[Embed("assets/directory/DefaultIcon.png")]
private static var _DEFAULT_ICON:Class;
// ... some more icons ...
override public function set data(value:Object):void
{
super.data = value; // let the base class take care of everything I didn't think of
if (value is Node) { // only handle the data if it's our own node class
switch ((value as Node).type) {
// ... some case clauses ...
default:
this._vSetIcon(_DEFAULT_ICON);
}
this.label.text = (value as Node).name;
}
}
private function _vSetIcon(icon:Class):void
{
if (null != this.icon && this.contains(this.icon)) {
this.removeChild(this.icon);
}
this.icon = new icon();
this.addChild(this.icon);
this.invalidateDisplayList();
}
}
This code has no effect whatsoever, icon and label in the tree control remain at their defaults. Using trace(), I verified that my code is actually executed. What did I do wrong?
Looking at the base mx.controls.treeClasses.TreeItemRenderer class, I see that in the updateDisplayList function the renderer gets it's icon and disclosureIcon classes from _listData:TeeListData. Instead of overriding the updateDisplayList function, try modifying the icon and disclosureIcon classes of the renderer's private _listData instance in your _vSetIcon method using the public accessors, like so:
private function _vSetIcon(icon:Class, disclosureIcon:Class = null):void
{
var tmpListData:TreeListData;
if (disclosureIcon == null) disclosureIcon = icon;
tmpListData = this.listData;
tmpListData.icon = icon;
tmpListData.disclosureIcon = disclosureIcon;
this.listData = tmpListData;
}
EDIT
Here is some clarification on the difference between data and listData. You'll have to excuse my omission of package names but I'm editing from my phone so its tough to look them up and I don't know the package names off the top of my head. data is defined in the context of a TreeItemRenderer in the IDataRenderer interface. You create a data renderer by implementing this interface and defining a public property data, which in this case is set by the parent control and contains some data and meta-data from the dataProvider to be rendered by the data renderer class.
listData is defined in the IDropInListItemRenderer interface as a property of type BaseListData and is realized in the TreeItemRenderer class as a property TreeListData. It differs from the data property in that it contains meta-data that describes the TreeListRenderer itself (icon, indent, open) as well as (I believe, I'll have to double check this later) a reference to the data item being rendered. I gather that It's used by the the TreeItemRenderer and I would imagine the parent list control for display update and sizing purposes. Someone is free to correct or add onto that if I'm incorrect or missed something, I'm going of what I remember drom the code.
In this case, you wanted to use meta-data from the data set from the data provider to modify data that determines the display of the renderer, so you would need to modify both.
I think the real confusion here however came from the fact that you extended the TreeItemRenderer class then tried to override functionality on the component in a manner the original developer didn't intend for someone to do, hence the unexpected results. If your goal is education and not ease of implementation you would probably be better served by extending the UIComponent class and using the TreeItemRenderer code as a reference to create a class that implements the same interfaces. That would be a real dive into the pool of custom component development.
I'd probably try something simple, as in this example from the Adobe Cookbooks. I notice that they override updateDisplayList, which may have something to do with your problems.
There's another example (for Flex 2, but looks applicable to Flex 3) that shows how to manage the default icons. It looks like you'll want to manage the icon yourself, setting the default icon styles to null, instead of trying to manipulate the superclass's icon property.
Update -- Looking at the source for TreeItemRenderer, commitProperties has the following before checking the data and setting up the icon and label:
if (icon)
{
removeChild(DisplayObject(icon));
icon = null;
}
Also, it looks like the setter for data calls invalidateProperties. Hence, your icon is wiped out when the framework gets around to calling commitProperties.
I am creating a canvas in actionscript like :
private var cvs_preview:Canvas = null;
private function show_preview():void
{
this.cvs_preview = new Canvas();
this.cvs_preview.id = "cvs_preview_1";
this.cvs_preview.setStyle('backgroundColor', 0x000000);
this.cvs_preview.setStyle('backgroundAlpha', 1);
this.cvs_preview.setStyle('borderColor', 0x417FDD);
this.cvs_preview.setStyle('cornerRadius', 10);
this.cvs_preview.setStyle('borderStyle', 'solid');
this.cvs_preview.setStyle('dropShadowEnabled', true);
var pt:Point = image.localToGlobal(new Point(image.x, image.y));
this.cvs_preview.x = pt.x - 50;
this.cvs_preview.y = pt.y - 50;
this.cvs_preview.height = 200;
this.cvs_preview.width = 250;
//this.cvs_preview.addEventListener(FlexEvent.CREATION_COMPLETE, get_focus_on_canvas);
//this.cvs_preview.focusManager.setFocus(
//this.cvs_preview.addEventListener(MouseEvent.CLICK, end_preview_on_focus_change);
this.cvs_preview.addEventListener(FocusEvent.MOUSE_FOCUS_CHANGE, end_preview_on_focus_change);
Application.application.addChild(this.cvs_preview); //add as top-most visible container
btn_mini_preview.enabled = false;
}
So on the focus change i want to run the "end_preview_on_focus_change()"
but this is not working.
As per my understanding, i think the canvas not getting any focus in the first place. I was trying to use focusManager.setFocus to do that after the canvas's creation complete. but even that is giving me an error.
the code i was trying on Creation.Complete is :
private function get_focus_on_canvas(e:FlexEvent)
{
focusManager.setFocus(e.target);
//Alert.show("testing img complete");
}
this is giving me an error "1118: Implicit coercion of a value with static type Object to a possibly unrelated type mx.managers:IFocusManagerComponent."
basically i just want to use the focus out event of the canvas.
Can someone help me with this...
I have been on this issue since a long time.
Regards
Zeeshan
The error is correct. You have an object of type Object which you are trying to use as an IFocusManagerComponent. This will not work. To accomplish that line of code, you need to do something like
focusManager.setFocus( IFocusManagerComponent( e.target ) );
This, of course, assumes that the target implements IFocusManagerComponent. It will give you an error otherwise (and likely will in this case because Canvas is not listed as an IFocusManagerComponent). The good news is that Canvas does have a drawFocus method which will accomplish the same thing.
As to your MOUSE_FOCUS_CHANGE event, that will only be fired if an object already HAS focus and then loses it. I think you are better off using FlexEvent.CREATION_COMPLETE. This will ensure that the component has registered itself with all of the appropriate classes in the Flex SDK so that the FocusManager can even be aware of the new object. Whatever you do, do not try to set focus on something which has not been added to the stage (ie: Event.ADDED has been called).
As another piece of advice -- Event.ADDED bubbles, make sure that event.currentTarget == event.target to make sure that you are listening to the correct object. Otherwise, you might be calling the same function multiple times erroneously.
Only a few classes implement IFocusManagerComponent as others mentioned and Canvas is not one of them. If you really must call FocusManager.setFocus() you will have to extend the canvas class to implement this interface and use that class instead. You don't have to write any methods to implement this interface, all methods have already been implemented by UIComponent itself
//FocusableCanvas.as (include appropriate package and import statements)
public class FocusableCanvas extends Canvas implements IFocusManagerComponent
{
public function FocusableCanvas()
{
super();
}
}
//Now use this class instead of Canvas
this.cvs_preview = new FocusableCanvas();
//setFocus in creation complete handler
FocusManager.setFocus(IFocusManagerComponent(e.target));
But if all you want to do is to set focus on the canvas upon it's creation, you can call canvas.setFocus() from the creationComplete handler instead.
private function get_focus_on_canvas(e:FlexEvent)
{
Canvas(e.currentTarget).setFocus();
trace("done");
}
I see two problems, and no perfect solutions. With any luck, this can help you out.
First of all, e.target returns an object typecast with type Object. This explains your implict coercion error, because Object does not implement IFocusManagerComponent.
Second, iFocusManagerComponent is only implemented by Accordion, AdvancedListBase, Button, ButtonBar, ChartBase, ComboBase, DateChooser, DateField, HTML, ListBase, MenuBar, NumericStepper, TabNavigator, TextArea, TextInput, UIMovieClip as per this entry in the Flex 3.4 AS3 Reference.
This leads me to believe that a Canvas element cannot take focus and has simply inherited access to the FocusManager through inheritance of UIComponent.
The only solutions I can see are to utilize something other than Canvas to handle your focus related concerns, or subclass Canvas and implement iFocusManagerComponent, though that looks fairly complex.
Edit
Apologies for missing drawFocus in the above solution.
Please try;
private function get_focus_on_canvas(e:FlexEvent)
{
this.cvs_preview.setFocus();
}