Data binding parent-child relationships in Aurelia - javascript

The Code:
I have two classes:
export class Shipment {
shipmentId: number;
widget: Widget;
}
export class Widget {
widgetId: number;
name: string;
}
Then I have a ShipmentUi view-model that has an instance of shipment (this.shipment).
And in the ShipmentUi view I compose part of the UI show the WidgetUi that allows selection of the Widget:
<compose view-model="src/views/widgetUi" model.bind="shipment"></compose>
The WigetUi's view-model saves off the shipment. So WidgetUi has a this.shipment.
And then widgetUi's view shows a selector:
<select value.bind="shipment.widget" >
<option class="dropdown-toggle" repeat.for="widget of widgets"
model.two-way="widget">${widget.name}</option>
</select>
The Question Setup:
In my compose tag (on ShipmentUi's view), I would rather bind to shipment.widget.
This would have WidgetUi's view-model only get a this.widget. (The WidgetUi class does not need to see or know about the shipment. Its sole function is to allow selecting a Widget. It should not need to care if it is for a shipment or for something else.)
But, as I understand Javascript, this is not going to work.
Because if I just pass in the reference to shipment.widget, then WidgetUi will have just a reference to the widget part. At first WidgetUi's this.widget will have the same reference as ShipmentUi's this.shipment.widget.
But when the user selects a different widget, WidgetUi this.widget will get a different reference (to the newly selected widget in the dropdown). But ShipmentUi's this.shipment.widget will will still reference the original widget.
The Question:
When binding to child objects in Javascript, do you always have to pass in the containing object if you want to know about swap of the child object?
The reason for this question is that my tests are not 100% conclusive. So I am hoping someone can clear it up for me.
I am also hoping I am wrong somehow, as I really don't like the idea of having to expose all the data in the containing classes. (Giving access to shipment in the WigetUi class in this case.)
Re-asking (clarification):
Fabio Luz requested some clarification on what I am asking. So here is an attempt. This walks through the example above, but changing it to the way I would LIKE it to work.
I have two Widgets. Sharp Widget and Dull Widget.
ShipmentUi.js:
This class has the variable this.shipment.widget. I am going to say that its value is 'A3' (arbitrary memory value). 'A3' is a reference to a widget that has a name of 'Sharp Widget'.
I then pass the widget down to the WidgetUi class:
<compose view-model="src/views/widgetUi" model.bind="shipment.wiget"></compose>
WidgetUi.js:
The WidgetUi class has:
activate(widget: Widget) {
this.widget = widget;
}
So now in WidgetUi this.widget also has a value of 'A3'. That value is a memory reference to the Widget that has a name of 'Sharp Widget'.
Now the user uses this select element to change the Widget:
<select value.bind="widget" >
<option class="dropdown-toggle" repeat.for="widget of widgets"
model.two-way="widget">${widget.name}</option>
</select>
This time I bind to widget (instead of this.shipment.widget like I did above).
Then user picks a widget with the name of 'Dull Widget' using the select. That widget has a value of 'B7'. And 'B7' is a reference to the widget named 'Dull Widget'.
As I understand JavaScript, WidgetUi's this.widget now has a value of 'B7' (which is a reference to 'Dull Widget'). (This is done via the Aurelia data binding system.)
But ShipmentUi's this.shipment.widget is still 'A3' (which is a reference to 'Sharp Widget').
This is not what I wanted when I bound this.shipment.widget to the compose element. I wanted the updates to the widget object to be reflected in the shipment. (Note, if I had just updated widget.name, then it would have been updated.)
So, from what I can see, I have to pass in the full parent to the compose element (this.shipment in this case), if I want an assignment to be captured.
I am hoping I am wrong (or there is a workaround), because passing the parent object makes me share details that the "child" class does not need to know about. (ie it breaks data encapsulation)
I guess I could make a "holder" between each layer of my classes. For Example: this.shipment.holder.widget and holder would just have the widget in it. But this is kinda ugly... I hope there is another way...
So, my question is: Am I right with my above statements? And if I am, is there another way that keeps my object model clean?

If I understand the question correctly you're looking for a way to share the minimum amount of data with the widgetui component. Instead of giving it the whole shipment object so that it can manipulate the shipment.widget property, you'd rather give it a property accessor to the widget property.
Good news: this is exactly what #bindable is designed to do. All you'll need to do is stop using compose and craft a custom element with #bindable properties representing the minimum amount of data the custom element needs to do it's job. For example:
widget-picker.js
import {bindable, bindingMode} from 'aurelia-framework';
export class WidgetPicker {
#bindable({ defaultBindingMode: bindingMode.twoWay }) widget;
#bindable widgets;
}
widget-picker.html
<select value.bind="widget">
<option repeat.for="widget of widgets" model.bind="widget">${widget.name}</option>
</select>
usage:
<widget-picker widget.bind="shipment.widget" widgets.bind="widgets"></widget-picker>
Example:
https://gist.run/?id=4c726da335aaecefd80b

Related

Angular component creation best practices

I'm often in doubt when I want a new behaviour of a component.
Let's make a simple example, I have <app-title> component:
<div>
<h1>{{title}}</h1>
</div>
Some time after, inside another page I need to put a button alongside the title. The problem is, should I create a new title component or should I parametrize the existing one?
I can edit <app-title> to look like this:
export class AppTitleComponent implements OnInit {
#Input() showButton: boolean;
title = 'App title';
constructor() {}
ngOnInit() {}
}
<div>
<h1>{{title}}</h1>
<button *ngIf="showButton">{{buttonTitle}}</button>
</div>
This is a simple example and may be obvious but using Angular I always have this problem, think about complex components: the #Input() would become many using this method, but creating a new component would increase files and complexity.
From this example you could say to create two components, one for title and another for button but that's only because this is a very simple case. Think about changing a component from "compact" mode to "expanded" and viceversa. On the one hand you may need to have the large component and on the other hand have it smaller in size and showing less information
Is there some guideline about this?
Thanks
I think it's important thinking about behavior within the context of your component. Is the button core to behavior of the title component? Does it make sense to not only display the button, but also handle its events within the context of the title component? If the answer is no, then at some granular level I'd split the components.
Here are some other things you can consider:
Anticipating that your title component may need some content wrapped with the title, you can use transclusion:
<div>
<h1>{{title}}</h1>
<ng-content></ng-content>
</div>
Then, in the parent, you'd do something like this:
<div>
<app-title-component title='title'>
<button>Some Button Text</button>
</app-title-component>
</div>
You could write a wrapper component that packages the title and the button together... ie:
<div>
<app-title-component></app-title-component>
<button>Some Button Text</button>
</div>
You could, as suggested, parameterize the configuration. I recommend thinking about if the behavior that you are parameterizing is part of the core behavior of the component. For example, if you want to paramaterize whether or not a legend shows on a chart, that makes sense because the legend is a core feature of the chart. But I probably wouldn't parameterize whether or not the chart should be followed by a raw data sheet. Instead I would create a new component for that and render them in sequence, because the data sheet is not part of the core behavior of the chart, even though sometimes I'd want to put them next to each other.
At the end of the day, you have to think about the decision in terms of your app, your app's future usability, and developer ease (e.g.- will it make sense to a future developer that this button is packaged with title).
I hope you find this helpful.

How to get an value from Child component to Parent on nuxt.js?

I am stuck trying pass data from Child A ($emit) component to Parent and from Parent to Child B (props).
Using nuxt.js I have:
layouts/default.vue
This default template will load a lot of components.
Those components will be used or not based on variable from child, the variable will set the v-if directive.
The children are the pages like:
pages/blog/index.vue
pages/about/index.vue
...
The goal is the Child set on Parent what components would be used, the flag can change anytime, the user can choose what will be rendered on admin area.
I have tried use local computed methods on child component, and vuex, no luck with both.
The idea on layouts/default.vue.
<template>
<div>
<TopBar v-if=showTopBar></TopBar>
<Nav v-if=showNav></Nav>
etc...
<nuxt />
</div>
</template>
<script>
import TopBar from "../components/TopBar";
import Nav from "../components/Nav";
etc...
export default {
data() {
return {
showTopBar: false,
showNav: false
etc...
};
},
}
</script>
On child already have use the $emit but no luck.
Child on this situation are pages, and the layout of those pages will be defined by variable from a fetch on the API, user can change the layout anytime.
The goal is have someting like double way between Child Components, example:
Calling route /blog will call pages/blog/index.vue
This would send to layout/default.vue using $emit what components would be rendered (choosed from user in admin area and fetched from API) and the component ID. (example: {topBar: true, topBarID: 2})
On layouts/default.vue after get the $emit from pages/blog/index.vue I would have for example TopBar false, and then not render it, or have received true with an ID, this Id will be send to TopBar as prop for render the customized TopBar made by user on Admin area.
Would be possible someone show an example how to get the pass those data for this specific cenario please?
(Does not matter if using local variables from the Child component or vuex, just looking for an example how to get the contents of variable from Child instead an plain object or undefinied object).
PS.: If there an better approach to deal with dynamic layouts, I am accepting suggestions too.
PS2.: I know I would use specific template per page, like layout/blog and layout/contact, etc... but since the idea is make an CMS, this would not fit on this scenario, I mean, from the admin area user should be able to create pages enabling or disabling components through an page Wizard (the idea is getting something like Wix, every component customization from user will be stored in the database using an Id, and on layouts user choose the previous components mounting the page, in the end all call will be made using the ids of those), and not need to add specific layouts programing, because this the Idea of set all possible components and layouts in layout/default.vue sounds at this moment an better approach, but if is not, I would love see other ways to get same goal.
The correct way to do it would be:
<child-component-1 :showNav.sync="showNav">
And within the child component you would update that by doing:
this.$emit('update:showNav', value)
The parent would define this property:
data() {
return {
showNav: default_value
}
}
You would have to pass that variable to every child component. Every child component would have to define it as a property.
Perhaps a better way to do it would be to instead create a simple store within nuxt and use that to house the settings.

In AngularJS, how can I nest variable child directive(s) inside a parent directive?

Introduction
For the project I am working on, I am trying to tackle a particular problem in the 'angular way', however I think I must be missing something because no matter what I try I continue to reach brick wall.
The crux of this issue is I am dynamically loading data from a backend that describes different components that are visible to the user. That's not the issue itself, but rather the issue of the particular & proper 'angular' way to turn a list of 'models' describing the components into actually rendered HTML.
Problem
What I am trying to create is basically the following:
Start off with a parent directive that uses ng-repeat for a scoped list called "models", which contains zero or more "components":
<parent-directive ng-repeat="model in models" model="model"></parent-directive>
The ng-repeat directive creates N copies of that original directive with different 'model' arguments (for each object in the $scope.models array).
// this is just for demonstrative purposes, it obviously looks different in source
<parent-directive model="child1"></parent-directive>
<parent-directive model="child2"></parent-directive>
<parent-directive model="child3"></parent-directive>
issue! => The parentdirective gets transformed into a specific child directive depending on data (in this case, called 'type') contained within the javascript object:
<parent-directive model="..."></parent-directive>
turns into
<child-directive-one model="..."></child-directive-one>
or
<child-directive-two model="..."></child-directive-two>
dependent on what the value 'model.type' is.
The child directive then renders into it's own custom HTML (outside the scope of this problem) using data passed to it. If we continued the example from above, that HTML should render into the following (hopefully):
<child-directive-one model="child1"></child-directive-one>
<child-directive-one model="child2"></child-directive-one>
<child-directive-two model="child3"></child-directive-two>'
Followed by (and this is outside the scope of the issue but just to see it through to the end) each directive rendering into its own HTML:
<div>in childDirectiveOne, text is: This is text contained inside child1</div>
<div>in childDirectiveOne, text is: This is text contained inside child2</div>
<div>in childDirectiveTwo, text is: This is text contained inside child3</div>
Source
I've been trying lots of different variations of things to try and get it to work (involving the link function, using $compile, etc), but this source is provided with all of those attempts stripped out. Here's the source I've developed so far:
removed source (was filled with errors). Solution that Scott helped me out with is below:
Conclusion
Thanks for any advice in advance.
Update:
Solution exists here (thanks again to Scott).
I'm not sure exactly why you can't just have a single directive, however something like the following might work. Instead of repeating the parent directive you just pass in the models and have that directive repeat and create each of the child directives.
<parent-directive the-models="models"></parent-directive>
Parent directive template:
<div ng-repeat="model in models"....>
<child-directive ng-if="YOUR CONDITION"></child-directive>
<child-directive2 ng-if="YOUR CONDITION"></child-directive>
</div>

Disable Jquery UI widget completely

How do I do such a thing? I need to disable the widget completely, meaning that all of the instances should be disabled and NO MORE instances can be created after the disabling. I tried searching, but nothing comes up.
Will appreciate any help.
EDIT:
A more lively example.
Say, I have three instances of my widget placed on three elements. Then I want to turn my widget off. I invoke a static method turnOff, which leads to
a) all working instances to be disabled
b) prohibit any other instances of that widget to be created if they are later called via ajax i.e.
Then I want it to work again, so i invoke a turnOn().
My widget is a hint pugin, so if the user switches hints off, they should be switched off everywhere, and there are places in the app where hinted parts of the page are still being loaded asynchronosly.
That's pretty much what I need to do.
The answer depends on if your widget is obvious when not active -- for example if it's a 'hint' type tooltip that shows on hover then all you need to do is not show the tool tip when it's inactive. However if it also adds formatting to show where the hints are you need to undo that as well.
Let's take the simple case first -- suppose we have a simple widget filler that just adds a class (filled) when the mouse is over the element. Then just have a variable in global scope:
enableFiller = true;
and then check that in your widget:
$.widget( "spacedog.filler", {
_create: function() {
var progress = this.options.value + "%";
this.element.hover(
function() {
if (enableFiller) {
$(this).addClass("filled");
}
},
function() {
$(this).removeClass("filled");
}
);
}
});
Fiddle.
Note that I don't check the flag in the removeClass because the widget might be disabled while the mouse is over a widget and you probably don't want the hover color to accidentally 'stick on'.
From there it's pretty easy to extend to the case where your widget alters the element even when off. I'll do the logic as pseudo-code:
When creating the widget add a base-widget class to the element (even if inactive)
If the widget is on add the active-widget class and do whatever is required
To turn off remove the active-widget class from all base widgets: $(".base-widget").removeClass('active-widget'). You might also need to do some other changes to all the elements -- but they're easy to find.
To turn on add the active-widget class back and adjust everything else
You can embed the turnOn/turnOff functionality into the widget, as long as it uses the global variable as the flag. See this answer for other options for storing the global: How to declare a global variable in JavaScript?
If you get stuck you could post (a simplified version) of your widget code and what you've tried so far and someone can probably help further.

Ember.js Navigation Controller - am I on the right path?

I'm currently building a reporting platform for internal use and decided to use Ember.js. So far it's been both a good and bad experience; the bad is mostly related to the documentation and how most things I've researched online have changed with the latest revisions of ember.
We are using twitter bootstrap and the navigation portion of bootstrap has the active class on the li element instead of the a element. Naturally my first inclination was to just use jquery as a hack and just change the active class manually which felt totally wrong and decided to find the 'right' way.
So I ended up building a custom view for the navigation, see below: (note: I am using browserify)
// NavigationView.js
module.exports = Ember.View.create({
templateName: 'navbar',
// Bind the 'selected' property on this view to the controllers 'selected' property.
selectedBinding: 'AnalyticsApp.NavigationController.selected',
// a single sub item for the nav
NavViewElement: Ember.View.extend({
// Change the tag name to be a LI tag since bootstrap requires active class
// to exist on that, instead of the default (ember) anchor tag when using {{linkTo}}
tagName: 'li',
// Bind the 'active' class to the computed property; checking if this nav
// element is the current active tab.
classNameBindings: ['isActive:active'],
// This computed property will check if this nav item is active
isActive: function() {
return this.get('item') === this.get('parentView.selected');
}.property('item', 'parentView.selected')
})
});
Now, setting up the view was pretty straight forward, to use it to render a nav element I can use this:
{{#view view.NavViewElement item="network" }}
{{#linkTo 'network'}}
<i class="icon-sitemap"></i>
<span>Networks</span>
{{/linkTo}}
{{/view}}
In all the routes in the setupController method I am setting the 'selected' tab like so
AnalyticsApp.NavigationController.set('selected', 'network');
Now here is where I am unsure regarding my implementation and I would really appreciate if someone could tell me if I'm way off target or if I am on the right path.
I am using the NavigationController (below) to be the central store for the navigation logic, it is actually an ObjectController that I've extended and chained .create() on.
AnalyticsApp.NavigationController = Ember.ObjectController.extend({
selected: null
}).create();
I tried extending a standard controller, but that doesn't expose the set / get methods. Is using an ObjectController for this type of setup the right type?
Thanks for taking the time to read, and I appreciate any and all feedback.
-Ryan S.
Since my comment was usefull, I'll convert it to an answer. So, since your NavigationController is used app wide, try just to create the controller like so:
AnalyticsApp.NavigationController = Ember.ObjectController.create({selected:null});
Hope it helps

Categories