Best way to implement a tabpanel in ember? - tabs

I'm new to ember and trying to build a Ember driven web application. I've read various tuts and studies several examples. The basic concepts are clear but now I'am stuck on trying to implement a tabpanel. My approach is as follows:
View
Configurator.TabPanelView = Ember.View.extend({
classNames: ['tabPanel'],
templateName: 'tabPanel'
});
Template
<script type="text/x-handlebars" data-template-name='tabPanel'>
<div class='tabHead'>
<ul>
{{#each tabViews}}
<li {{action "{{this.actionName}}" target="{{this.value}}"}} >{{this.title}}</li>
{{/each}}
</ul>
<div class="tab-content">{{outlet}}</div>
</div>
</script>
Usage in App
var tab= Configurator.TabPanelView.create({
classNames: ['assortment'],
tabViews: [{ title: 'First', value:'Foo', actionName: 'firstTab' },{title: 'Second', value:'Foo', actionName: 'secondTab' }],
firstTab: Ember.View.extend({
templateName: 'first'
}),
secondTab: Ember.View.extend({
templateName: 'second'
})
});
tab.appendTo("body");
The TabTemplate is rendered correctly but if I try to click on the li-elements following error is thrown
Uncaught Error: assertion failed: Target <(subclass of
Ember.View):ember217> does not have action {{this.actionName}}
I'm also curious if I should use a router to implement tabbing. But as far as i can see routers act on application level and are intended to be used in single UI-compos.

The problem is in your template:
<li {{action "{{this.actionName}}" target="{{this.value}}"}} >{{this.title}}</li>
AFAIK, actions can't be bound, so when you write this, it tries to call the method {{this.actionName}} instead of firstTab, for example.
I think this is a typical example where you should use a Ember.CollectionView with an itemViewClass which has the click method, i.e.:
App.MyCollectionView = Ember.CollectionView.extend({
tagName: 'ul',
templateName: 'the-template-name',
itemViewClass: Ember.View.extend({
click: function() {
var actionName = this.get('content.actionName'),
target = this.get('controller.target');
target.send(actionName);
}
})
});
The code above is surely not right, but the idea is here.
But I think the Router is the right way to do that. I suggest you to take a look at the Ember Router example by #ghempton, where it defines tab with Ember.Router.

You have 2 options:
1) each tabpage has its own controller, view and must also be defined in the router
<script type="text/x-handlebars" data-template-name="tabs">
<div>
<ul class="nav nav-tabs">
{{#view Bootstrap.TabItem item="info"}}
<a {{action gotoInfo}}>Info</a>
{{/view}}
{{#view Bootstrap.TabItem item="anamnese"}}
<a {{action gotoAnamnese}}>Anamnese</a>
{{/view}}
{{#view Bootstrap.TabItem item="medication"}}
<a {{action gotoMedication}}>Medication</a>
{{/view}}
</ul>
{{outlet}}
</div>
</script>
Bootstrap.TabItem = Ember.View.extend({
tagName: 'li',
classNameBindings: ['isActive:active'],
isActive: function() {
return this.get('item') === this.get('controller.selectedTab');
}.property('item', 'controller.selectedTab').cacheable()
});
2) all tabpages are in one large view, and tabpages will be hidden or shown
{{#view Ember.TabContainerView currentView="info"}}
<ul class="nav nav-tabs">
{{#view Bootstrap.TabView value="info"}}<a>Info</a>{{/view}}
{{#view Bootstrap.TabView value="anamnese"}}<a>Anamnese</a>{{/view}}
{{#view Bootstrap.TabView value="medication"}}<a>Medication</a>{{/view}}
</ul>
{{#view Ember.TabPaneView viewName="info"}}
{{view EEPD.InfoView}}
{{/view}}
{{#view Ember.TabPaneView viewName="anamnese"}}
{{view EEPD.AnamneseView}}
{{/view}}
{{#view Ember.TabPaneView viewName="medication"}}
{{view EEPD.MedicationView}}
{{/view}}
{{/view}}
Bootstrap.TabView = Ember.TabView.extend({
tagName: 'li',
classNameBindings: ['isActive:active'],
isActive: function() {
return this.get('value') === this.get('tabsContainer.currentView');
}.property('tabsContainer.currentView').cacheable()
});

There are two ways to implement a tab panel.
If you want your tabs to be bookmarkable, then you should implement them using Router:
Templates
<script type="text/x-handlebars" data-template-name="application">
<div class="tabpanel">
<div class="tabs">
<div {{action "goToFirstTab"}}>First tab</div>
<div {{action "goToSecondTab"}}>Second tab</div>
</div>
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="firstTab">
First Tab content
</script>
<script type="text/x-handlebars" data-template-name="secondTab">
Second Tab content
</script>
Code:
var App = Ember.Application.create();
App.ApplicationController = Ember.Controller.extend();
App.ApplicationView = Ember.View.extend();
App.FirstTabView = Ember.View.extend({templateName: "firstTab"});
App.FirstTabController = Ember.Controller.extend();
App.SecondTabView = Ember.View.extend({templateName: "secondTab"});
App.SecondTabController = Ember.Controller.extend();
App.Router = Ember.Router.create({
root: Ember.Route.extend({
goToFirstTab: Ember.Route.transitionTo("firstTab"),
goToSecondTab: Ember.Route.transitionTo("secondTab"),
index: Ember.Route.extend({
route: "/",
redirectsTo: "firstTab"
}),
firstTab: Ember.Route.extend({
route: "/firstTab",
connectOutlets: function (router) {
router.get('applicationController').connectOutlet('firstTab');
}
}),
secondTab: Ember.Route.extend({
route: "/secondTab",
connectOutlets: function (router) {
router.get('applicationController').connectOutlet('secondTab');
}
})
})
});
App.initialize(App.Router);
The second way, without Router.
Templates (note that actions` targets are changed)
<script type="text/x-handlebars" data-template-name="application">
<div class="tabpanel">
<div class="tabs">
<div {{action "goToFirstTab" target="controller"}}>First tab</div>
<div {{action "goToSecondTab" target="controller"}}>Second tab</div>
</div>
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="firstTab">
First Tab content
</script>
<script type="text/x-handlebars" data-template-name="secondTab">
Second Tab content
</script>
Code (pretty much the same, except that the code related to tabs is now moved to ApplicationController.
var App = Ember.Application.create();
App.ApplicationView = Ember.View.extend();
App.Router = Ember.Route.create();
App.FirstTabView = Ember.View.extend({templateName: "firstTab"});
App.FirstTabController = Ember.Controller.extend();
App.SecondTabView = Ember.View.extend({templateName: "secondTab"});
App.SecondTabController = Ember.Controller.extend();
App.ApplicationController = Ember.Controller.extend({
view: App.FirstTabView.create(),
goToFirstTab: function () {
this.connectOutlet("firstTab");
},
goToSecondTab: function () {
this.connectOutlet("secondTab");
}
});
App.initialize(App.Router);

Related

How to create a bullet list from parsed dynamic object

So I have a JSON string foo_json_string:
[{"foo_name":"foo_value"},{"foo_name1":"foo_value1"}]
that I want to parse and show as HTML list.
I used the following approach:
<ul>
<li v-for="item in JSON.parse(foo_json_string)" v-bind:key="item.id">
{{`${item.name} = ${item.value}`}}
</li>
</ul>
This doesn't work. Most likely because item.name and item.value don't exist but I am not sure how to fix that. Any suggestions?
Maybe, you mean like this ?
var respond = [{"foo_name":"foo_value"},{"foo_name1":"foo_value1"}];
$.each(respond, function(k, v){
console.log(v);
$.each(v, function(k1, v1){
$('#out').append('<li>'+v1+'</li>');
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul id="out"></ul>
You need to access the key of the parsed Object. Use Object.keys() method for getting the key. There is the working snippet below.
new Vue({
el: '#app',
data: {
foo_json_string: '[{"foo_name":"foo_value"},{"foo_name1":"foo_value1"}]',
},
});
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<ul v-for="item in JSON.parse(foo_json_string)">
<li v-for="key in Object.keys(item)">
{{`${key} = ${item[key]}`}}
</li>
</ul>
</div>

Angular 2-way binding not working

Below is my code.2 way binding is not working as expected. Can you please point-out the mistake
<div id="list" class="form-group">
<label for="attach" class="col-xs-2 control-label">{{ resource["gve.common.attach"] }}</label>
<div class="col-xs-5">
<ol class="marTop10">
<li class="exactFit" ng-repeat="files in attachList">{{ files.fileName }}</li>
</ol>
</div>
</div>
$scope.populateView = function()
{
$rootScope.inputSpin = false;
$rootScope.modifiedCaseData = $scope.problem;
$scope.showReviewBtn = $rootScope.showReviewBtn;
$scope.attachList = [];
$scope.attachList.push({fileId:"100",fileName:"Test.pdf"});
for(i in $scope.attachList) {
console.log (i, $scope.attachList[i]);
}
};
fileName is not getting displayed in HTML {{files.fileName}} even though {fileId:"100",fileName:"Test.pdf"} is added to $scope.attachList
EDIT 1: I am sorry for not mentioning those details. ng-controller is already defined and populateView() is called from a different function.
EDIT 2: From console.log .
09:56:17.486 problemCtrl.js?gccm=1.0:185 0 Object {fileId: 100, fileName: "untitled 8.txt"}fileId: 100fileName: "untitled 8.txt"
Are you missing the ng-controller directive or it is already defined elsewhere?
Make sure you are calling the populateView()
DEMO
var app = angular.module('test',[]);
app.controller('testCtrl',function($scope){
$scope.populateView = function()
{
$scope.attachList = []; $scope.attachList.push({fileId:"100",fileName:"Test.pdf"});
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test" ng-controller="testCtrl" ng-init="populateView()" class="col-xs-5">
<ol class="marTop10">
<li class="exactFit" ng-repeat="files in attachList">{{ files.fileName }}</li>
</ol>
</div>

AngularJS $rootScope not accessible in html page

I have looked for the solution in following two links:
how-to-access-rootscope-value-defined-in-one-module-into-another-module
rootscope-variable-exists-but-not-accessible
But still could not find a solution.
I initialize $rootScope.user variable in $scope.login function, which is triggered by some other button click event, of my controller as:
app.controller('LoginFormController',['$rootScope','$location','$log','$scope','userAngService','userBean',function($rootScope,$location,$log,$scope,userAngService,userBean){
$scope.login = function(val){
$scope.user = userAngService.login(val).then(function(data){
$rootScope.user = data;
$location.path("/home");
});
};
}]);
Redirecting it to /home mapped in app.js as:
angular.module('Writer', ['AllControllers','ngRoute'])
.config(['$routeProvider', function($routeProvider,$Log){
$routeProvider.when('/Diary',{
templateUrl:'login.html',
controller: 'LoginFormController'
})
.when('/home',{
templateUrl: 'home.html',
controller: 'ReachController'
})
.otherwise({redirectTo : '/Diary'});
}]);
Now I am accessing $rootScope.user variable in the mapped controller ReachController as:
app.controller('ReachController',['$scope','$rootScope',function($scope,$rootScope){
$scope.user = {};
$scope.user = $rootScope.user;
console.log($scope.user);
}]);
It is perfectly logging angular object in console but is not avaiable in my home.html page.
Here is the html page - accessing it in <h2> and <h3> tags:
<div>
<h2>{{$scope.user}}hhhello</h2>
<h3>{{$scope.user.author}}</h3>
<div id="welcomeStory">
<span id="wsTitleBub">Title</span>
<input id="wsTitle" type="text" ng-model="wsPublish.welcomeStoryTitle" />{{wsPublish.welcomeStoryTitle}}
<h6>Words..</h6>
<textarea id="wsWords" ng-model="wsPublish.welcomeStoryWords"></textarea>
<input id="wsPublish" type="button" name="wsPublish" value="Publish" ng-click="pub = !pub" />
<input id="wsAddShelf" type="button" name="wsAddToShelf" value="Add To Shelf" ng-click="addToShelf()" />
</div>
<div id="wsPublishTo" ng-show="pub">
<ul>
<li>
<input type=submit id="wsgroups" value="Group" ng-click="publishGroup(wsPublish)" />
</li>
<li>
<button id="wsPublic" ng-click="public()">Public</button>
</li>
</ul>
</div>
</div>
Just change your $scope.user to user in your markup and it'll work.
Once it is in $rootScope you can directly access it in markup with {{user}} no need to again assign it to scope variable!

How to keep entered data of page1 after navigating back from page2 in AngularJS

I have three pages with routing. If I entered some input and getting some result on first page, next I navigated to second page and finally I came back from second page to first page. Then I want to see the data what I entered previously and result also.
Here the code snippet
index.html
<!DOCTYPE html>
<html ng-app="scotchApp">
<head>
<script src="angular.min.js"></script>
<script src="angular-route.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-controller="mainController">
<nav class="navbar navbar-default">
<div class="container"></div>
<ul class="nav navbar-nav navbar-right">
<li><i class="fa fa-home"></i> Home</li>
<li><i class="fa fa-shield"></i> About</li>
<li><i class="fa fa-comment"></i> Contact</li>
</ul>
</div>
</nav>
<div id="main">
<div ng-view></div>
</div>
</body>
</html>
home.html
<div class="jumbotron text-center">
<h1>Home Page</h1>
Billing index :
<input type="text" ng-model='billingMapValue'>
<br/><br/>
Billing value :
{{billingMap[billingMapValue]}}
<ng-click=navigate() type="button" value='submit'>
<p>{{ message }}</p>
</div>
about.html
<div class="jumbotron text-center">
<h1>About Page</h1>
<p>{{ message }}</p>
</div>
script.js
var scotchApp = angular.module('scotchApp', [ 'ngRoute' ]);
scotchApp.config(function($routeProvider) {
$routeProvider
.when('/', {
templateUrl : 'home.html',
controller : 'mainController'
})
.when('/about', {
templateUrl : 'about.html',
controller : 'mainController'
})
.when('/contact', {
templateUrl : 'contact.html',
controller : 'mainController'
});
});
scotchApp.controller('mainController', function($scope) {
$scope.message = 'Everyone come and see how good I look!';
$scope.billingMapValue = "";
$scope.billingMap = new Array();
$scope.billingMap["ZF2"] = "Invoice";
$scope.billingMap["ZRE"] = "Credit for Returns";
$scope.billingMap["ZG2"] = "Credit Memo";
$scope.billingMap["ZL2"] = "Debit Memo";
$scope.billingMap["ZS2"] = "Cancellation of Credit Memo";
$scope.billingMap["ZS1"] = "Cancel. Invoice (S1)";
});
Now what I need is. If I run index.html page, I will be in home page there is one input text box. If enter some index value like 'ZF2' I will see the value "invoice". there will be list of hyperlinks on top of page .home, .about and .contact. I will click about item then I navigate to about page. Then I navigate again to home page by clicking home hyperlink , now I need to see the previous data which I entered and got.How to do that?
Thanks in advance.
I'd suggest you to use service, that will act as sharable resource between the different controllers.
You need to do some changes in your code.
You need to move all the static to either service or angular constant.
Use dot rule while binding object that will udpate your binding automatically.
Assign a different controller for each view, that would be more modular approach.
Service
scotchApp.service('dataService', function() {
this.data = {}
this.data.billingMap = new Array();
this.data.billingMap["ZF2"] = "Invoice";
this.data.billingMap["ZRE"] = "Credit for Returns";
this.data.billingMap["ZG2"] = "Credit Memo";
this.data.billingMap["ZL2"] = "Debit Memo";
this.data.billingMap["ZS2"] = "Cancellation of Credit Memo";
this.data.billingMap["ZS1"] = "Cancel. Invoice (S1)";
this.data.selectedBillMap = '';
});
Controller
scotchApp.controller('mainController', function($scope, dataService) {
$scope.message = 'Everyone come and see how good I look!';
$scope.billingData = dataService.data.billingMap;
});
Demo Plunkr

How do I update the model on click only (angular-strap typeahead and json)

I'm having trouble figuring out how to have dynamic data only update when the user selects from the typeahead menu or clicks the search button.
Right now, the dynamic content pertaining to the search query updates automatically when the input value is changed (content disappears). I want the content to stay in view until a new selection has been either clicked in the typeahead list or clicked by the search button.
Any insight at all would be greatly appreciated! Thank you!
Plunker demo:
http://plnkr.co/edit/jVmHwIwJ0KOKCnX6QjVa?p=preview
Code:
<!-- HTML -->
<body ng-controller="MainController">
<!-- Search -->
<div class="well">
<p>Search the term "content"</p>
<form role="form">
<div class="form-group clearfix search">
<input type="text" ng-model="selectedContent" ng-options="query as query.searchQuery for query in searchData" bs-typeahead="bs-typeahead" class="form-control search-field"/>
<button type="button" class="btn btn-primary search-btn"><span class="glyphicon glyphicon-search"></span></button>
</div>
</form>
</div>
<!-- Dynamic Content -->
<div class="well">
<h4>{{ selectedContent.contentTitle }}</h4>
<ul>
<li ng-repeat="item in selectedContent.headlines">{{item.headline}}</li>
</ul>
</div>
<!-- typeahead template -->
<ul class="typeahead dropdown-menu" tabindex="-1" ng-show="$isVisible()" role="select">
<li role="presentation" ng-repeat="match in $matches" ng-class="{active: $index == $activeIndex}">
</li>
<!-- JS -->
var app = angular.module('demoApp', ['ngAnimate', 'ngSanitize', 'mgcrea.ngStrap'])
.config(function ($typeaheadProvider) {
angular.extend($typeaheadProvider.defaults, {
template: 'ngstrapTypeahead.html',
container: 'body'
});
});
function MainController($scope, $templateCache, $http) {
$scope.selectedContent = '';
$http.get('searchData.json').then(function(response){
$scope.searchData = response.data;
return $scope.searchData;
});
};
You could use a directive such as this:
app.directive('mySearch', function(){
return {
restrict: 'A',
require: 'ngModel',
link: function($scope, $element, $attrs, ngModel){
ngModel.$render = function(){
if (angular.isObject($scope.selectedContent)) {
$scope.clickedContent = $scope.selectedContent;
}
}
$scope.updateModel = function() {
$scope.clickedContent = $scope.selectedContent;
}
}
}
})
plunker
Edit:
I added using the ngModelController. The function you set ngModel.$render to gets called whenever the model updates. If you click the typahead popup, then the model selectedContent will be an object, otherwise it'll be a string. If it's an object (meaning the user clicked the typahead popup) we do the same as we did in the updateModel function.