EmberJS, Helper with an "as" block - ember.js

I have an Object and then I have a Decorator that consumes the Object and returns a Human-friendly representation of the Object.
For example:
object = { id: "XXX1", detail: "XXX2" }
decoratedObject { title: "The Red Building", detail: "Has 101 stories" }
I would like to use the Decorator in a template:
{{#decorator-helper object as |decoratedObject|}}
<h1>{{decoratedObject.title}}</h1>
<p>{{decoratedObject.detail}}</p>
{{/decorator-helper}}
How can I do this?

You should use a component for this.
Generally you should use helpers if you want to use them to calculate other values like this:
{{my-component value=(my-helper val)}}
If you want to use block syntax you should use a component.

As far as I know you cannot do this with helpers. Why don't you define a computed property called decoratedObject on your controller or component that would be based on object ?
decoratedObject: Ember.computed('object', {
get() {
// Build your decorated object and return it
return decoratedObject;
}
})
This would make your decorated object available in your template and dynamically rebuild it when object changes.

Related

How to pass a #tracked object from an Ember route model hook

My question is two-fold:
Where is the best place to put some kind of polling logic - in the route file right?
How do I pass this constantly updating value from the Route to some child component? Labeling some variable as "#tracked" and then passing the tracked variable via the model hook?
Let's say I have something like this:
routes/index.js
export default class IndexRoute extends Route {
#tracked
recent: {
A: 0,
...
},
constructor() {
super(...arguments);
this.getRecent();
}
getRecent() {
// poll data / fetch latest
const {A, ...partialObject} = this.recent;
this.recent = { ...partialObject, A: <some new value fetched>};;
later(this, this.getRecent, 2000);
}
model() {
return this.recent;
}
}
application.hbs
<p>constantly updating "this.recent": {{ this.model.A }} </p>
I thought if I use the model hook like this, it would be tracked and therefore auto-update but that was not the case. I have this sample Ember Twiddle that emulates what I'm trying to do. I tried to force a re-compute by reassigning the entire variable but it didn't work.
This question is a deeper dive from my initial question here.
You are returning a reference to object stored in this.recent in your model hook. But the getRecent method does not change that object but overrides this.recent. After the first execution of getRecent method the model of the route and this.recent aren't the same object anymore. The model of the route, which you can access through this.modelFor(this.routeName) is the initial value and this.recent is the new value.
You want to mutate the object returned from model hook instead.
The object given in your example has a fixed schema. This allows you to mark the property A as tracked:
recent: {
#tracked A: 0,
...
}
As currently you return the value of this.recent in your model hook. But instead of overwriting it in getRecent method, you are only changing the value of it's property A:
getRecent() {
this.recent.A = <some new value fetched>;
later(this, this.getRecent, 2000);
}
If you don't know the schema of the object returned in model hook or if you are dealing with an array, it's a little bit more complicated. You wouldn't have a property to decorate with #tracked. I would recommend to use the tracked-built-ins package in that case.
For arrays you can also fallback to legacy MutableArray from #ember/array/mutable package. But you must make sure in that case that you use it's custom methods to manipulate the array (e.g. pushObject instead of push).

Qml: pass argument as part of URL when loading components from files

Is there a mechanism to pass URL arguments in Qml and extract them later?:
StackView.push("Page.qml?#label2);
Regards,
You can "pass arguments" when creating an object from a component.
StackView.push(component.createObject(null, {"someProperty" : someValue}))
So you can use an auxilary component to facilitate that:
Component { id: component; url: "Page.qml" } // or
property Component component: Qt.createComponent("Page.qml") // or
Component { id: component; Page {} }
Or if you don't want to pollute with extra stuff, you can directly:
StackView.push(Qt.createComponent("Page.qml").createObject(null, {"someProperty" : someValue}))
Lastly, not a good idea to have your StackView named StackView, I mean in QML neither properties nor ids can begin with uppercase character.
No need to create an object manually. Just do this
stackView.push(Qt.resolvedUrl("qrc:/U/R/L/item.qml"), {someCustomProperty: 0})
This is NOT worked:
navigationBar.push(Qt.resolvedUrl('qrc:/Pages/BookListPage.qml'), {argument:'test'})
But this is worked (to create an object):
navigationBar.push(Qt.createComponent("qrc:/Pages/BookListPage.qml").createObject(null, {argument:'test'}))
Actually according to the QT docs ([http://doc.qt.io/qt-5/qml-qtquick-controls-stackview.html][1]) the recommended way is to pass a property list containing at least the the following minimum entries to the push function ():
item: this property is required, and holds the item to be pushed.
properties: a list of QML properties to be assigned to the item upon push. These properties will be copied into the item at load
time, or when the item will become the current item (normally upon
push).
so we get
navigationBar.push({item: Qt.resolvedUrl("MyRectangle.qml"), properties: {"color" : "red"}});

computed.oneWay not working with Ember Data

I have an EmberData snapshot which I'd like decorate with a few additional attributes before handing over to the UI for presentation. This decoration will be setting "properties" of the Ember-Data record not actual attributes. It looks like this:
let foo = Ember.computed.oneWay(this.get('store').find('activity'));
foo.map(item => {
item.set('foobar', 'baz');
return item;
}
I would then hope that foo would be the beneficiary of the promised record (it is) and the setting of the foobar property would be localized to the foo property (it's not, it seems to be globally scoped to the record's properties).
As for me this is expected behavior.
1) OneWay means only you are not bind set method on foo. It used to work between properties of objects, but this.get('store').find('activity') is a just promise.
2) foo is store.find() result of DS.RecordArray type (assuming promise is resolved). So you are iterating on records returned and set foobar property on them.
Achieving your goal, you could decorate activity record by component (Ember 2.0 way) for UI presentation.
So far the only way I've been able to do this is using Ember's ObjectProxy like so:
const FooDecorator = Ember.ObjectProxy.extend({
foobar: 'baz'
});
let foo = Ember.computed(function() {
return this.get('store').find('activity').map(item => {
FooDecorator.create({content: item});
});
}
This works but I thought I'd been hearing things about Object proxies not being a welcome part of Ember 2.0 roadmap so not sure if this approach is best. I'll keep this open for a few days before closing.

TypeError: Object #<Object> has no method 'set'

once a certain process is done I need to set a boolean to true in order to update the template.
I can easily get the object, but setting a property seems to be more difficult. What I use to get the object is
var found = self.get('content').findProperty('id', self.datasetid);
If I do that in the chrome console I can clearly see that I get an ember object back:
Object {id: 1, active: true}
__ember1364221685101_meta: Meta
active: true
get data_set: function () {
id: 1
set data_set: function (value) {
__proto__: Object
When I do:
found.set('data_set.fully_geocoded', true);
I do get the error mentioned in title. I've tried as many different flavours as I could think of, but all with the same result.
Could somebody shine a light on this?
An Object isn't an instance of Ember.Object, but the base Javascript class Object, so it won't have a get and set method.
You can get much of the same functionality by using Ember.get and Ember.set directly, passing in the object, as such:
Ember.set(found, 'data_set.fully_geocoded', true)
Ember.get(found, 'data_set.fully_geocoded')
Computed properties and observers can also fire based on using Ember.set this way.

How do I test rendered views when using T4MVC with TestHelper?

How do I test which view was rendered from a controller action if what I get is a T4MVC_ActionResult? Under normal circumstances I should be able to directly use TestHelper's methods, like in the examples:
pooController.Details().AssertViewRendered().ForView("Details")
...but, since through T4MVC I get a T4MVC_ActionResult instead of a ViewResult, the part AssertViewRendered<>().ForView("Details") fails. What alternative do I have if I want to test which view was invoked?
UPDATE:
Here's the test code:
[TestMethod]
public void Theme_Controller_Details_Action_Returns_Details_View()
{
var builder = new TestControllerBuilder();
var mockThemeRepository = new Mock<IThemeRepository>();
var themeController = builder.CreateController<Evalgrid.Website.Controllers.ThemeController>(mockThemeRepository.Object);
builder.InitializeController(themeController);
var result = themeController.Details();
result.AssertViewRendered().ForView("Details");
}
I used the debugger setting a breakpoint after the result line, and its variable type is T4MVC_ActionResult, while themeController is Evalgrid.Website.controllers.ThemeController. Note that I have used the fully qualified name of the controller.
I get this:
Expected result to be of type
ViewResult. It is actually of type
T4MVC_ActionResult.
I don't know what's going on.
Actually, T4MVC should not make a difference here. If you directly instantiate your controller and call an action method, you'll get the same thing back whether you use T4MVC or not. i.e. you won't get a T4MVC_ActionResult.
It's only when you write MVC.Foo.Details() that you'll get a T4MVC_ActionResult. That's because MVC.Foo returns an instance of a derived class which does special thing, and not directly your controller class.
Does that make sense?
Update: I'm confused, as looking at the sources for TestControllerBuilder.CreateController, it has:
public T CreateController<T>(params object[] constructorArgs) where T : Controller
{
var controller = (Controller)Activator.CreateInstance(typeof(T), constructorArgs);
InitializeController(controller);
return controller as T;
}
So it's directly instantiating the type that you pass in, which should just call your normal action.
One question about your code: does your Details action method take any parameters? If so, that would explain the problem, as you're calling it with no params, which would be a T4MVC method added in the partial class.