I have an Ember.js component called table-viewer. It has a toolbar which is a div with buttons. I have another ember component called report-viewer. It contains a variety of things including a table-viewer.
I want to have report-viewer add some buttons to the toolbar. What I have works but breaks the Ember connection with the element so I can't change the button text after moving it. Is there a better way to do this?
I have a lot more components than I just said, so defining the complete toolbar in table-viewer and setting flags for what to show would be annoying to manage.
Below is what I currently have in table-viewer so that any component can add additional buttons to the toolbar:
Ember.$('#toolbar').append(Ember.$('#additionalToolbar').html());
Ember.$('#additionalToolbar').remove();
I figured it out! The following works without breaking Ember connections:
Ember.$('#additionalToolbar > button').appendTo(Ember.$('#toolbar'));
It's hard to say what the best approach is without seeing the relevant code. That said, my first suggestion would be a simple yield:
report-viewer/template.hbs
{{#table-viewer}}
<button>
Some button from the report viewer...
</button>
{{/table-viewer}}
table-viewer/template.hbs
<nav>
<button>
Button one
</button>
<button>
Button two
</button>
{{yield}}
</nav>
<table>
...
</table>
If you need more that one yield, you can achieve multiple named yields with this approach.
I'd also suggest splitting the toolbar out into its own component if you haven't already. It feels wrong to put a toolbar button as the block contents of the table-viewer component as I've done above.
Related
I just upgraded my Ember addon from version 3.0 to version 3.8, and I get this warning now:
Interaction added to non-interactive element no-invalid-interactive
An example of this is something like:
<div class="some-class" onclick={{action "selectDate" "today"}}>
<div> more content </div>
</div>
When you click the action, it should take you to a new route.
What are my options to fix it the right way, so that it is accessible?
The solution here depends on what the action should do.
Refactoring an action that triggers a transition
Since this action takes the user to a new route, it should be a link element, <a> and not a button. This gives assistive technology/screen reader users a clue that interacting will take them somewhere new in the app.
This can be refactored to a link-to in Ember, which will wrap the content in <a>:
{{#link-to someRoute}}
<div> more content </div>
{{/link-to}}
someRoute could be a computed property if it needs to be informed by multiple pieces of data. Otherwise, it could be a string.
Refactoring an interaction that keeps you on the same page
In cases where the action does not take you to a different page, it may be appropriate to use a <button>. An example of this would be a button that expands a collapsible container or toggles a setting. However, the w3 validator tells us that you can't put divs inside of buttons. You can use Phrasing Content that is non-interactive, such as <span>.
<button class="some-class" onclick={{action "toggleSomething"}}>
<span> more content </span>
</button>
Learn More
To catch more accessibility problems in an app, try out ember-a11y-testing which audits your app for problems and gives you a report of how to fix them.
This question was answered as part of "May I Ask a Question" Season 2 Episode 3. If you would like to see us discuss this answer in full you can check out the video here: https://youtu.be/nQsG1zjl8H4
Quick note:
I don't believe this is a duplicate of Ember.js: Prevent destroying of views. Other related questions that I've found are out-of-date.
In case this becomes out-of-date later, I am using Ember 1.7.0 with Handlebars 1.3.0.
Context for the question:
As the title states, I am wondering how to transition between views without destroying them. Using queryParams does not solve my issue.
I am creating a calculator with the following nested views:
>>Calculator View
>>Report View (hasMany relationship to Calculator)
--School Partial (I am using queryParams here)
I am able to navigate between the Report views just fine without destroying my School partial, since I am using queryParams and using a displaySchoolPartial boolean to show/hide the partial. Example below:
Report template (stripped to only show the essential part):
<script type="text/x-handlebars" data-template-name="calculator/report">
...
{{#link-to "calculator.report" (query-parameters displaySchoolPartial="true")}}
{{render "_school"}}
</script>
School template (also stripped down):
<script type="text/x-handlebars" data-template-name="_school">
{{#with controllers.calculatorReport}}
<div {{bind-attr class=":schoolPartialWrapper displaySchoolPartial::hide-element"}}>
...
</div>
{{/with}}
</script>
This works as expected. Navigating between different Report views and School partials, as stated before, does not destroy the view.
The problem:
My problem comes when navigating to the Calculator view, the Report view is destroyed, which then destroys my School view. I do not want to also use queryParams to replace my Report views.
The reason I need to make sure the views aren't destroyed is because I have a select box with 3,000 schools in my School partial. It takes too long to re-render this. It would be a much better UX to simply show/hide the Report views.
Don't fight with Ember. You will lose.
Views are instantiated and rendered when needed and torn down when done.
Why do you have a 3000-element dropdown, anyway?
If you really, really want to do this, what I would suggest is putting a {{render}} on your application page, and hide it. The view will be created and rendered when the app comes up and persist as long as the app is alive. Then, in the didInsertElement of your view, do a cloneNode of that hidden element and insert it into the view's DOM somewhere. You may have to muck around getting event handlers wired up correctly.
My suggestion is not using "render" but using "partial", so you only need to drop in the template that you want. Have a control variable that set show/hide via css class. And control that variable using you controllers.
Using "partial" will allow you to have school template independent from report, thereby removing report will not affect school.
Just make sure you define the outlet and partial correctly.
Hope it helps!
What is the best way of toggling the presence of some element (represented by a template) that is a direct child of <body>?
I'm talking about a modal box, notification, light-box etc. that is triggered either by some user event or by a route.
Examples: Dialog for newsletter sign-up that is shown after a user clicks a button. Overlay for content editing that is triggered by appending /edit to the item's route
Edit:
The first solution I though of was using Session to control state and then lining up all the application's modals and messages inside #if statements at the end of my main layout template like this:
<template name="layout">
<!-- yields and stuff -->
{{#if isActiveModal 'editArticle'}}{{> editArticle}}{{/if}}
{{#if ...
</template>
The problem is modularity; if a teammate is working inside some page template and needs to display a specific message or dialog, he or she has to edit the main layout to add it. I would have liked some easy way of conditionally appending a template to <body>.
Should be possible with the tools at hand, shouldn't it?
I actually use bootbox throughout several of my Meteor apps and it works great and does not interfere with any of Meteor's rendering. You can use the normal alert, confirm, prompt, as well as a custom dialog function, in a non-blocking way. There is a smart package available:
https://github.com/TimHeckel/meteor-bootboxjs
See some of my apps and smart packages for examples (grep for bootbox.):
https://github.com/mizzao/CrowdMapper/
https://github.com/HarvardEconCS/turkserver-meteor/
If you use the latest version of the new template engine, you can work with the body tag as its own special template through UI.body:
meteor update --release template-engine-preview-10.1
HTML:
<body>
{{#with alert}}
{{> modal}}
{{/with}}
</body>
<template name="modal">
<div class="modal">
X
<p>{{message}}</p>
</div>
</template>
JS:
if (Meteor.isClient) {
UI.body.alert = function() {
return Session.get("modal-alert");
};
UI.body.events({
"click .close": function() {
Session.set("modal-alert", null);
}
});
}
Since I posted the question, Blaze has become more sophisticated. Elements can now be rendered anywhere in the DOM while keeping a logical view hierarchy. See this discussion:
https://forums.meteor.com/t/most-useful-meteor-apis/1560/8
I've inserted the main outline below – the full thread also has code examples.
...
[Use] Blaze.render to output a template anywhere in the DOM – such as in a dimmable wrapper directly inside <body> – while maintaining a parent-child relationship between the view of the opening template and the view of the modal. This relationship is required for them to communicate directly.
(Bear in mind that a Session variable, among other limitations, can only hold serializable data...)
With the hierarchy intact, it's possible to set up shared reactivity between the modal and the background and call methods on the other, too. If we volunteered to use Blaze.renderWithData, then the two templates can even share the same data context!
Manually traversing the view hierarchy is pretty tedious, but with the parent() method from aldeed:template-extension, it isn't an issue.
Depending on whether one likes to use template instances as a 'view-model' or prefers to pass stuff around, reciprocal reactivity between the two templates can either be mediated by a new ReactiveVar assigned to a property on the parent template instance or view, or by a more retrieve-on-demand TemplateVar.
The entire exercise with Blaze.render can be duly incapsulated by using tricks like block helper templates and keyword arguments, so that everything is kept declarative.
I have a page that culminates in three buttons: a checkout button, a change-qty button, and a cancel button:
<button name="checkout" onclick="location.href='/checkout/'" >checkout </button>
<button name="change" onclick="location.href='/change-qty/'" >change qty</button>
<button name="cancel" onclick="location.href='/cancel-order/'">cancel </button>
I'm doing these redirects in this fashion, instead of using a form, because I want each button to redirect to a different Django view. The code above works fine, but isn't RESTful: "checkout" and "cancel-order" change the system's state, and so I'd like these to be POSTs.
I could achieve this by having the "checkout" and "cancel" views auto-post special-purpose forms to two new views that take POSTs. But that's so much screwing around and complexity-adding. I could also add some jQuery to the page, and have each of those buttons trigger a POST on a hidden form pointed toward the appropriate views, and achieve the same thing. But that, too, is an awful lot of extra code for something that seems pretty simple.
Really, if there was something like a 'method="post"' on a button, that would be perfect. But there isn't. Is there some elegant pattern for achieving what I want? Or am I doing something very stupid and I just don't know it?
Use jquery-ujs, and add "data-" attributes to your links:
change qty
etc. the unobtrusive JavaScript will inject the correct code into your link/button/whatever.
I was trying to fully understand the previous answer of the question emberjs - how to mark active menu item using router infrastructure
In the answer I tried to simplify (without using a outlet navigation and without adding a second level routing under the root) but in my case the active link doesn't work ://
Here's a fiddle :
http://jsfiddle.net/archange/Scqxw/
What I don't understand in the fiddle is why the "isActive: function()" is not updated after the set on the router... :/
So if someone passing by can explain me the reason. Big Thanks.
And if someone has another solution for handling navigation menu (perhaps using bindAttr ?)
Thanks
What is happening here is that when a template/view creates another view, it gives the newly created view access to it's controller. For example:
<script type="text/x-handlebars" data-template-name="application">
{{view App.NavigationView}}
</script>
The controller of the navigation view is going to be the instance applicationController. This is expected behavior.
If you want to tell your newly created NavigationView to use another controller, just pass that in, like so:
{{view App.NavigationView controllerBinding="navigationController"}}
Please note that I wired these two controllers together in App.ready.
Here is your edited fiddle: http://jsfiddle.net/Scqxw/5/