I designed a mockup for a website that has a sidebar with a menu that appears on mouseEnter, and disappears on mouseLeave. This was mocked up with jQuery, though now I'm trying to recreate this functionality using Ember. I have this working with mouseEnter correctly so far, but I can't figure out how to also bind mouseLeave.
From what I've read so far, implementing a View seems to be the answer though, since view's are deprecated I'm not sure how to go about this.
Here's what I have so far:
/app/controllers/sidebar.js
import Ember from 'ember';
export default Ember.Controller.extend({
title: 'Ticket Log',
menu_showing: false,
actions: {
toggleMenu: function () {
this.set('menu_showing', !this.get('menu_showing'));
console.log(this.get('menu_showing'));
}
}
});
/app/templates/sidebar.hbs
<div {{action "toggleMenu" on="mouseEnter"}} id="sidebar" class="panel panel-default">
{{#if menu_showing}}
<div id="sidebar-menu">
<div id="sidebar-menu-buttons">
<button id="sidebar-menu-toggle" type="button" class="btn btn-default glyphicon glyphicon-menu-hamburger"></button>
<button id="sidebar-menu-lock" type="button" class="btn btn-default glyphicon glyphicon-lock"></button>
</div>
<div id="sidebar-menu-pills" class="panel panel-default">
<div class="panel-body">
<ul class="nav nav-pills nav-stacked">
<li class="active">Ticket Log</li>
<li>Customer Info</li>
<li>Asset Info</li>
</ul>
</div>
</div>
</div>
{{/if}}
{{title}}
{{outlet}}
</div>
You are almost right. Views are deprecated but Components are not and they are extended from View.
Ember.Component.extend({
isMenuShowing: false,
mouseLeave() {
this.toggleProperty('isMenuShowing');
},
mouseEnter() {
this.toggleProperty('isMenuShowing');
},
});
Related
I'm using pageslide directive in order to display a lateral sliding menu when I click on an icon.
This is the usual way to do it, and its working :
<div class="navbar-header">
<span ng-controller="slideController as s">
<a class="navbar-brand" href="javascript:void(0)"
ng-click="s.toggle()"><i class="fa fa-bars fa-lg"></i></a>
<div pageslide ps-open="s.isActive" ps-side="left">
<div style="padding:20px">
<h2>Hello Pageslide</h2>
<p>Put here whatever I want in the lateral menu</p>
<a ng-click="s.toggle()" class="button" >Close</a>
</div>
</div>
</span>
<a class="navbar-brand text-danger" href="javascript:void(0)">Balrog</a>
</div>
But I would lilke to make the menu content in an external template, so I'm trying this :
<div class="navbar-header">
<span ng-controller="slideController as s">
<a class="navbar-brand" href="javascript:void(0)"
ng-click="s.toggle()"><i class="fa fa-bars fa-lg"></i></a>
<menu-item isActive="s.isActive"></menu-item>
</span>
<a class="navbar-brand text-danger" href="javascript:void(0)">Balrog</a>
</div>
with this as menu.html :
<div pageslide ps-open="s.isActive" ps-side="left">
<div style="padding:20px">
<h2>Hello Pageslide</h2>
<p>Put here whatever I want in the lateral menu</p>
<a ng-click="s.toggle()" class="button" >Close</a>
</div>
</div>
also, here is the related controller :
'use strict';
angular.module('BalrogApp').controller('slideController', function(){
this.isActive= false; // This will be binded using the ps-open attribute
this.toggle = function(){
this.isActive= !this.isActive
}
});
and the <menu-item> directive :
angular.module('BalrogApp').directive('menuItem', function () {
return {
restrict: 'E',
templateUrl: "views/menu.html",
scope: {
isActive: '='
},
controller: 'slideController',
controllerAs: 's',
bindToController: true
}
});
So it's not working this way, maybe it is because the main view (including the <menu-item>) is itself included in the main page thanks to <ng-view> ?
Or maybe the pageslide-directive requires the <div pageslide ...> and its parent element to be on the same file ?
I can tell from the console that toggle() is called and changes isActive in both cases but it's not opening the menu with the directive version.
And also, adding ng-controller="slideController as s" to the root <div> with the pageslide attribute didn't change anything.
So how can I make this work with the menu in another file ?
Did you try to replace <div ng-include="menu.html"></div> with <div ng-include="'menu.html'"></div>. According to https://docs.angularjs.org/api/ng/directive/ngInclude, you have to wrap string constants in single quotes.
I highly recommend you not use ng-include. You can get the same approach using directive instead.
For example:
Menu html
<div pageslide ps-open="vm.checked" ps-side="left">
<div style="padding:20px">
<h2>Hello Pageslide</h2>
<p>Put here whatever I want in the lateral menu</p>
<a ng-click="vm.toggle()" class="button" >Close</a>
</div>
</div>
Directive:
function Controller() {
}
Controller.prototype.toggle = function() {
this.checked = !this.checked;
}
angular
.module('BalrogApp')
.directive('menuItem', function() {
return {
templateUrl: 'urltomenu.html',
restrict: 'E',
scope: {
checked: '='
},
controller: Controller,
controllerAs: 'vm',
bindToController: true
}
})
Main template
<div class="navbar-header">
<span ng-controller="slideController as s">
<a class="navbar-brand" href="javascript:void(0)"
ng-click="s.toggle()"><i class="fa fa-bars fa-lg"></i></a>
<menu-item checked="s.checked"><menu-item>
</span>
<a class="navbar-brand text-danger" href="javascript:void(0)">Balrog</a>
</div>
This approach give you more power. All the logic is inside the directive controller and you could reuse this element each time. The scope attribute checked is the "bridge" between your main template if change outside also change inside and also the other way.
I must not be doing something right. I have the following:
application.hbs
{{#view App.NavbarView}}{{/view}}
{{outlet}}
with the following template for Navbar
_navbar.hbs
<div class="toolbar">
<div class="row">
<div class="absolute top-left">
<button {{action "back"}} class="btn passive back"><i class="fa fa-play"></i></button>
</div>
{{#if hasTabs}}
<div class="small-centered columns">
<div class="tabs">
<ul>
{{#link-to 'stories' tagName="li" class="tab"}}<i class="fa fa-book"></i> Stories{{/link-to}}
{{#link-to 'mylevels' tagName="li" class="tab"}}<i class="fa fa-user"></i> My Levels{{/link-to}}
{{#link-to 'arcade.index' tagName="li" class="tab"}}<i class="fa fa-gamepad"></i> Arcade{{/link-to}}
</ul>
</div>
</div>
{{else}}
<div class="small-6 small-offset-3 columns">
<h2 class="title">{{ pageTitle App.currentPath }}</h2>
</div>
{{/if}}
{{#if currentUser.userName}}
<div class="absolute top-right">
<span class="user-hello">Welcome Back, <strong>{{ currentUser.userName }}</strong></span>
<button {{action "transitionAccount" currentUser._id}} class="square logged-in"><i class="fa fa-user"></i></button>
</div>
{{ else }}
<div class="absolute top-right">
<button {{action "transitionLogin"}} class="square logged-out"><i class="fa fa-user"></i></button>
</div>
{{/if}}
</div>
</div>
So all it is is a typical fixed navbar and in the middle of it I display what page you are on, if you happen to be on a page that has tabbed content, I show a tab container instead.
So I'm just using this.get('currentPath') in my App controller and comparing it against a group of route names to trigger true/false (I need an observer so it looks at the route change since the Navbar is in inline view at the Application level).
app.js
App.ApplicationController = Ember.ObjectController.extend({
updateCurrentPath: function() {
App.set('currentPath', this.get('currentPath'));
}.observes('currentPath'),
tabs: function() {
var route = this.get('currentPath'),
group = ['arcade.index', 'mylevels', 'stories', 'arcade', 'arcade.loading'];
console.log("ROUTE: ", route);
var tabs = group.indexOf(route) > -1 ? true : false;
return tabs;
}.observes('currentPath'),
// no idea what to do here
hasTabs: function() {
this.tabs();
}.property('tabs')
});
So, basically, no matter what, the tab UI is showing up, but I only want it to show up if that tabs observer is true. With some debugging I'm getting all the console output I would expect but I tried just doing {{#if tabs}} (just using the observer directly) and that always fires true (always shows the tabs UI). I assumed that's because it was an observer and not an actual controller property I could use in my template, so I tried just setting the hasTabs property and referencing the observer, but that doesn't seem to work. I realize I am fundamentally not understanding how this should work. Any thoughts?
If I understand your question correctly you should be able to just change your code to this (renamed tabs to hasTabs, removed previous hasTabs function. Changed from observes currentPath to be property of current path, removed the tabs variable assignment and replaced with the return, reduced the boolean conditional to the simple comparison operator). This is what I'd do, anyway. :) H2H
hasTabs: function() {
var route = this.get('currentPath'),
group = ['arcade.index', 'mylevels', 'stories', 'arcade', 'arcade.loading'];
return group.indexOf(route) > -1;
}.property('currentPath')
I'm developing a small notifications-like module, where the user can see his 5 latest activities that are logged in the DB (MSSQL). The values that I need are all there, but for some reason knockout binding is not working. Here are code snippets:
<div class="dropdown-menu toolbar pull-right" data-bind="with: layoutLogsModel">
<h3 style="border: none;">Recent activities:</h3>
<!-- "mailbox-slimscroll-js" identifier is used with Slimscroll.js plugin -->
<ul id="mailbox-slimscroll-js" class="mailbox" data-bind="foreach: layoutLogsModel.notification">
<div class="alert inbox">
<a href="javascript:void(0)">
<i class="icon-book" style="color: orange;"></i>
Some text
</a>
<br>
Some text #2
</div>
</ul>
</div>
For now, I only want to display random text for every item that is in the observableArray.
ViewModel is the following:
var layoutLogsModel = {
notification: ko.observableArray()
};
function getLastFiveActivities() {
get(apiUrl + "Logs/GetLastFiveActivities", { ClientUserID: loggedUserID }, function (data) {
layoutLogsModel.notification(data);
});
}
And every time I call this function, the list is empty (IMAGE)
(the function is called on click, and absolutely no errors are shown in the console).
What is it that I am doing wrong?
EDIT:
The thing was, I forgot to execute ko.applyBindings for that viewModel. Then, I changed the HTML to look like this:
<ul id="mailbox-slimscroll-js" class="mailbox" data-bind="foreach: notification">
<div class="alert inbox">
<a href="javascript:void(0)">
<i class="icon-user" style="color: green;"></i>
<span data-bind="text: $data"></span>
</a>
</div>
</ul>
Aslo, I modified the get function slightly, like this:
function getLastFiveActivities() {
get(apiUrl + "Logs/GetLastFiveActivities", { ClientUserID: loggedUserID }, function (data) {
layoutLogsModel.notification(data.Notification);
});
}
(changed data to data.Notification based on the MVC model property that contains the array)
After all that, the data was available immediately.
try removing the layoutLogsModel from the foreach, you are already using it with the binding "with", so eveything in that div will be part of layoutLogsModel.
<div class="dropdown-menu toolbar pull-right" data-bind="with: layoutLogsModel">
<h3 style="border: none;">Recent activities:</h3>
<!-- "mailbox-slimscroll-js" identifier is used with Slimscroll.js plugin -->
<ul id="mailbox-slimscroll-js" class="mailbox" data-bind="foreach: notification">
<div class="alert inbox">
<a href="javascript:void(0)">
<i class="icon-book" style="color: orange;"></i>
Some text
</a>
<br>
Some text #2
</div>
</ul>
</div>
I'm writing an ember app that has this settings model that I'm using to bypass routing because I want a single url. However, I get this error:
Error: Cannot perform operations on a Metamorph that is not in the DOM.
when I change any of the data in the model. I'm relatively new to ember, but from what I read of what's out there, you should be able to change a model just fine. Here's my html file:
<script type="text/x-handlebars" data-template-name="index">
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="brand" href="#"><img src="./img/MySpending.png"></a>
<div class="nav-collapse">
<ul id="navWrapper" class="nav">
{{#if model.isIndex}}
{{partial "indexNav"}}
{{/if}}
{{#if model.isMonthly}}
{{partial "monthlyNav"}}
{{/if}}
{{#if model.isYearly}}
{{partial "yearlyNav"}}
{{/if}}
</ul>
</div>
<div class='navbar_form pull-right'></div>
</div>
</div>
</div>
<div id="bodyWrapper" class="container" style="padding-top:60px;">
{{#if model.isIndex}}
{{partial "indexPage"}}
{{/if}}
{{#if model.isMonthly}}
{{partial "monthlyPage"}}
{{/if}}
{{#if model.isYearly}}
{{partial "yearlyPage"}}
{{/if}}
</div>
{{outlet}}
And each partial works fine initially.
It's when I change it in the three regular functions in my app.js that I have a problem. Here's my app.js:
App = Ember.Application.create();
App.Router.map(function() {
// put your routes here
});
App.settings=Ember.Object.extend({
isIndex: true,
isMonthly: false,
isYearly: false
});
Settings=App.settings.create();
//App.SettingsController = Ember.ObjectController.extend(Settings); Not sure whether to put this in or not
App.IndexRoute = Ember.Route.extend({
model: function() {
return Settings;
}
});
function goToMonthly(){
Settings.set('isIndex',false);
Settings.set('isMonthly',true);
Settings.set('isYearly',false);
}
function goToYearly(){
Settings.set('isIndex',false);
Settings.set('isMonthly',false);
Settings.set('isYearly',true);
}
function goToIndex(){
Settings.set('isIndex',true);
Settings.set('isMonthly',false);
Settings.set('isYearly',false);
}
So I'm not sure where the error is but if anyone can help,it'd be extremely appreciated.
There's a fundamental error in your approach here. Your Route/Controller are meant to handle the model for you, so by declaring your model outside of these objects, you're making more work for yourself and leading yourself to potential trouble.
I'm not seeing where your "goTo" functions are being called (I assume they're inside the partials, but if they're in that HTML and I missed it, that's my bad). But here's how they should be referenced:
App.SettingsController = Ember.ObjectController.extend({
actions:{
goToMonthly:function(){
this.set('isIndex', false);
this.set('isMonthly', true);
this.set('isYearly', false);
},
//Other functions go here
}
});
Then within your html, call these actions using handlebars:
<a {{action 'goToMonthly'}}> Click this to run the action </a>
Which ember version are you using? I tried it with ember 1.2 and 1.3 and your example is working fine.
I want to use Bootstrap 3's modal along with Ember.js. So far, I have been unsucessfull. The data modal itself is not appearing, although the screen is doing the fade.
my app.js:
App = Ember.Application.create();
App.ApplicationRoute = Ember.Route.extend({
events: {
showGroups: function() {
this.container.lookup('view:groups').append();
}
}
});
App.GroupsRoute = Ember.Route.extend({
model: function() {
return groups;
}
});
App.GroupsView = Ember.View.extend({
templateName: "groups",
classNames: ["modal", "fade"],
didInsertElement: function() {
this.$().modal('show');
this.$().one("hidden", this._viewDidHide);
},
// modal dismissed by example clicked in X, make sure the modal view is destroyed
_viewDidHide: function() {
if (!this.isDestroyed) {
this.destroy();
}
},
// here we click in close button so _viewDidHide is called
close: function() {
this.$(".close").click();
}
});
var groups = [
{
id: 1,
name: 'Family Reunion',
},
{
id: 2,
name: 'California Trip',
},
{
id: 3,
name: 'Dream Vacations',
},
{
id: 4,
name: 'Fun for Kids',
},
];
My templates:
<script type="text/x-handlebars" data-template-name="application">
<button class="btn actionBtn userControls" {{action showGroups}}><span class="glyphicon glyphicon-heart"></span>   My Favorites</button>
<!-- Split button -->
<div class="btn-group">
<button type="button" class="btn secondaryBtn userControls"><span class="glyphicon glyphicon-user"></span>   Hello, <b>User</b></button>
<button type="button" class="btn secondaryBtn dropdown-toggle userControls" data-toggle="dropdown">
<span class="caret"></span>
</button>
<ul class="dropdown-menu secondaryMenu" role="menu">
<li>Account Settings</li>
<li>Logout</li>
</ul>
</div>
</script>
<script type="text/x-handlebars" data-template-name="groups">>
<div class="modal fade" id="favoritesModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title tertiaryHeading">My Favorite Groups</h4>
</div>
<div class="modal-body">
{{#each model}}
{{render "group" this}}
{{/each}}
</div>
<div class="modal-footer">
<button type="button" class="btn actionBtn">Create New Group</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</script>
<script type="text/x-handlebars" data-template-name="group">
<button class="btn secondaryBtn groupBtn">{{name}}</button>
</script>
How can I make this work? I'm new to Ember, and not sure if I understand how to inject data into a view?
You have two issues that are slowing you down.
First, your 'groups' template is never being inserted into the DOM. this.container.lookup('view:groups').append() does not do what you seem to think it does. The Ember Way (tm) is to use an outlet for rendering nested templates, and modals are really just a special case of nesting. (You can use the View Tree of the Ember inspector to see that the 'groups' template is never inserted.)
I would recommend altering your ApplicationRoute to be this (use actions instead of events, and just transitionTo the 'groups' route):
App.ApplicationRoute = Ember.Route.extend({
actions: {
showGroups: function() {
this.transitionTo('groups');
}
}
});
At this point you should see that the 'groups' template is making it into the DOM, but it's still not being shown. This is due to the second issue, which is that you have a modal inside a modal (yo dawg!). Your GroupsView is creating a new element and setting class names of modal and fade on the new element. Your groups template is being rendered into that element, but the outer div in your groups template has the class names of modal and fade. The outer modal (created by GroupsView) is being "displayed", but since it contains content that is explicitly hidden (due to the modal fade classes) nothing is shown on screen. You could either remove the outer div in your template, or remove the class names from GroupsView, and then display the modal with this.$('#favoritesModal').modal('show').
Here's a working JSBin : http://jsbin.com/ucanam/1311/edit