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

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

Related

Data binding parent-child relationships in Aurelia

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

Angular 2 add custom component to Owl Carousel 2

I have created two components in Angular 2:
ReaderComponent: The one that initiates and controls all functionality to Owl Carousel (initiate, add slide, remove slide and so on)
PageComponent: Each slide is a PageComponent and has events to handle input from the user (click, pinch, doubletap)
The ReaderComponent is created at start of the application and initiates a request to a service to get all data for each of the PageComponents.
Everything works fine until we add a slide that is a PageComponent. I have tried to add the PageComponent selector to owl Carousel:
this.slider.trigger("add.owl.carousel", ["<my-page-component></my-page-component>"]);
This does add an element of <my-page-component> but does not render the template or handles any of the PageComponents events.
I have tried to add all the PageComponents to an array and render it in ReaderComponents template:
<div *ng-for="#page of pages">
<my-page></my-page>
</div>
This renders correct but by that time all pages is rendered Owl is already initiated and no pages is visible.
So to summarize all of this: I need to know how to add a custom component via javascript (in this case the add functionality of Owl)? Is this even possible? Or is there another way to handle this so that I can add PageComponent in any way?
The first method you mentioned would require you to force angular to re-check it's bindings. This is probably possible, but I don't know off the top of my head.
The second method is much easier. You can use the lifecycle events of the Page or Reader Components to trigger the adding. They are as follows:
export var LIFECYCLE_HOOKS_VALUES = [
LifecycleHooks.OnInit,
LifecycleHooks.OnDestroy,
LifecycleHooks.DoCheck,
LifecycleHooks.OnChanges,
LifecycleHooks.AfterContentInit,
LifecycleHooks.AfterContentChecked,
LifecycleHooks.AfterViewInit,
LifecycleHooks.AfterViewChecked
];
If you add a listener to your PageComponent class, you can probably use OnInit or AfterViewChecked and then get it to add it's own element reference to the carousel (basic example). From a quick look at their documentation, it doesn't look like owl supports adding a new element, so you could have the PageComponents all on your page somewhere hidden and then just add the raw html from the elementref, then remove it again in the OnDestroy function.
If you do it in ReaderComponent you should look at OnChanges or add some clever checks into the DoCheck function and then just get it to reload all items inside it (perhaps owl.reinit?). I've not used the owl carousel before so can't be more specific there I'm afraid.
These are exported from the angular class as interfaces, so you should be extending your classes from them. An example is available on the Angular 2 website here: https://angular.io/docs/ts/latest/api/lifecycle_hooks/AfterViewChecked-interface.html

Dynamic views based on content in an Ember.js CMS

I'm very new to both Ember and web development. Let's say that I'm building a site that displays news articles. I'd like some interaction with Wikipedia links in each article so that when you click on them, a little box will pop up in place with the summary (like the Instapaper app). I'd also like some interaction within the box, expanding and collapsing the summary, etc. What is the most Ember-like way to do this?
I think that with jQuery it would be trivial to add a click handler to the links to add the box to the DOM, but I'd really like the box to be an Ember view. I thought I might have been able to create a view dynamically and append it to a DOM element but it seems that it isn't allowed, and you can only append to ContainerViews.
How should I approach this?
Update on what I've tried
Essentially, I have this as a template for each article so there are paragraphs and a container on the right of each paragraph for the boxes:
{{#each paragraph}}
<div class='paragraph-container'>
<p {{bind-attr id=paragraph_id}}>{{html_text}}</p>
<div class='paragraph-reference-container' {{bind-attr id=paragraph_reference_id}}></div>
</div>
{{/each}}
And an associated view. paragraph_reference_id and paragraph_id are computed properties so I can set the IDs properly here. I also have a ReferenceView for the boxes, which is uninteresting since it's just placeholder content and code at the moment. I was racking my brain trying to figure this out, and ended up trying something like this to append the view as was in the documentation:
App.ArticleView = Ember.View.extend({
templateName: 'article',
classNames: ['articles'],
didInsertElement: function () {
var container = this.container;
Ember.run.scheduleOnce('afterRender', this, function () {
var em = this;
em.$('.paragraph-container').each(function () {
var referenceContainerID = $('.paragraph-reference-container', this).first().prop('id');
em.$('a.wikipedia-link', this).click(function () {
var referenceView = container.lookup('view:reference');
referenceView.appendTo('#' + referenceContainerID);
});
});
});
}
});
But I got the "Uncaught Error: Assertion Failed: You cannot append to an existing Ember.View. Consider using Ember.ContainerView instead." error. Apparently this is no longer supported as mentioned above. I don't really know what the "Ember" way is to do this and still use views (which I'd really like to do) instead of plain HTML and jQuery.

setting a class to a menu depending of the url link

Here is my scenario, my sub pages are not recognizing the parent, probably cause I set the structure bad, however going to back to fix that is not an option for now, problem the menu parent is getting the parent class in the wrong places....
So what I need to achieve...
if I have this url structure: domain.com/products/.... assign "current" class to menu X...
If I have this other: domain.com/sales/..... assign "current" class to Y....
I know I can do it via javascript, however I lack of knowledge of it, I'm just looking for a solid starting point.
I would appreciate any help.
Thanks in advanced.
You can check the url and do your class assignment like this:
if(document.location.href.indexOf('products') > 0){
$("#somediv").addClass("current");
}
Without providing some sample of you're structure I can't really put together a more meaningful sample but you can see maybe how this would work out.

Getting classNameBindings working with a button #view in handlebars with ember.js

I'm trying to get a class to appear on a specific button withing a handlebar view based on a property binding. I'm doing something that is like the Todo app that ember.js has on their site (http://emberjs.com/examples/todos/) and I'm trying to make the "Clear Completed" button disappear based on the value of a property.
I have a jsfiddle showing kind of what I'm going for here (http://jsfiddle.net/boushley/XEdNg/). If I add a className inside of the #view tag it shows up fine. But if I add a clasNameBindings it doesn't work the way I expect. Am I going about this wrong or is something broken here?
Aaron
I remember I had similar problems with classNameBindings too.
Try this instead: http://guides.sproutcore20.com/using_handlebars.html#binding-class-names-with-bindattr.
// Javascript for the view
App.AlertView = SC.View.extend({
priority: "p4",
isUrgent: true
});
// Template
<div {{bindAttr class="priority"}}>
Warning!
</div>
// Emits the following HTML
<div class="p4">
Warning!
</div>
If you just want to show/hide the button, then try binding the isVisible property.
isVisibleBinding: 'App.pageController.myProperty'
Hope this helps.
I discussed this here https://github.com/emberjs/ember.js/issues/322 and one of the people on the project helped with this, giving a clear way of how to perform this here https://github.com/emberjs/ember.js/issues/322#issuecomment-3332477 (http://jsfiddle.net/aNZSD/) In essence I needed to create a view that extends ButtonView and place the properties I want on this new view. This is the way it should work, I just wasn't seeing this properly.
Hope this makes it clearer for someone else.

Categories