Sticky states are reinitializing controller - tabs

I have problems implementing sticky tabs where each tab can have multiple child states using ui-router-tabs and ui-router-extras.
When I open a state, move to annother and then go back to the first state, the corresponding controller is getting reinitialized (although the ui-router-extras debug output says it reactivated the state)
When I open another state of the same tab (sibling state), the debug output, the url and the request of the html template is telling me that the new state is loaded, but the tab is showing an empty content and the api call of the state is not executed
This is my state/route configuration:
$stateProvider
.state('home', {
url: '/',
templateUrl: 'modules/core/views/home.client.view.html'
})
.state('home.feed', {
abstract: true,
url: '',
template: '<div ui-view="list" ng-show="$state.includes(\'home.feed.list\')"/>'+
'<div ui-view="view" ng-show="$state.includes(\'home.feed.view\')"/>'+
'<div ui-view="profile" ng-show="$state.includes(\'home.feed.profile\')"/>',
data: {
roles: ['user', 'admin']
},
sticky: true,
deepStateRedirect: true
})
.state('home.feed.list', {
url: 'posts',
views: {
'list#home.feed': {
templateUrl: 'modules/posts/views/list-feed.client.view.html'
}
},
sticky: true
})
.state('home.feed.view', {
url: 'posts/:postId',
views: {
'view#home.feed': {
templateUrl: 'modules/posts/views/view-post.client.view.html'
}
},
sticky: true
})
.state('home.create', {
abstract: true,
url: '',
template: '<div ui-view="form" ng-show="$state.includes(\'home.create.form\')"/>',
data: {
roles: ['user', 'admin']
},
sticky: true,
deepStateRedirect: true
})
.state('home.create.form', {
url: 'create',
views: {
'form': {
templateUrl: 'modules/posts/views/create-post.client.view.html'
}
},
sticky: true
});
The "home" state is containing the ui-view for all tabs as well as the navigation bar. Each abstract state is representing a single tab and contains the named views for all its child states. The 3rd level of the states (e.g. home.feed.list) is representing the actual content.
//home
<div data-ng-controller="HomeController">
<ui-view></ui-view>
<div class="navbar navbar-fixed-bottom">
<tabs data="tabData" type="tabs" justified="true" template-url="modules/core/views/custom-tab-template.client.view.html"></tabs>
</div>
</div>
//home.feed.list
<section data-ng-controller="PostsController" data-ng-init="loadPosts()">
...
</section>
//home.feed.view
<section data-ng-controller="PostsController" data-ng-init="findOne()">
...
</section>
//home.create.form
<section data-ng-controller="PostsController">
...
</section>
The views are using the same controller, but I already tried to add a seperate controller for each view. Furthermore I tried to remove the ui-router-tabs and called the states by url, same result.
Example debug output for: home.feed.list -> home.create.form -> home.feed.list
(Sry not enough reputation to post pictures)
Current transition: : {}: -> home.feed.list: {}
Before transition, inactives are: : []
After transition, inactives will be: []
Transition will exit: []
Transition will enter: ["ENTER: home", "ENTER: home.feed", "ENTER: home.feed.list"]
SurrogateFromPath: []
SurrogateToPath: ["home", "home.feed", "home.feed.list"]
I am initializing PostsController
Current state: home.feed.list, inactive states: []
Views: (__inactives.locals) / (root.locals) / (home.locals: '#' (home)) / (home.locals: '#' (home)) / (home.feed.locals: '#home' (home.feed)) / (home.feed.list.locals: 'list#home.feed' (home.feed.list))
Current transition: home.feed.list: {}: -> home.create.form: {}
Before transition, inactives are: : []
After transition, inactives will be: ["home.feed", "home.feed.list"]
Transition will exit: ["(home)", "INACTIVATE: home.feed", "INACTIVATE: home.feed.list"]
Transition will enter: ["(home)", "ENTER: home.create", "ENTER: home.create.form"]
SurrogateFromPath: ["home", "inactivate:home.feed", "inactivate:home.feed.list"]
SurrogateToPath: ["home", "home.create", "home.create.form"]
I am initializing PostsController
Current state: home.create.form, inactive states: ["home.feed.list", "home.feed"]
Views: (__inactives.locals: '#home' (home.feed), 'list#home.feed' (home.feed.list)) / (root.locals) / (home.locals: '#' (home)) / (home.locals: '#' (home)) / (home.create.locals: '#home' (home.create)) / (home.create.form.locals: 'form#home.create' (home.create.form))
Current transition: home.create.form: {}: -> home.feed.list: {}
Before transition, inactives are: : ["home.feed.list", "home.feed"]
After transition, inactives will be: ["home.create", "home.create.form"]
Transition will exit: ["(home)", "INACTIVATE: home.create", "INACTIVATE: home.create.form"]
Transition will enter: ["(home)", "REACTIVATE: home.feed", "REACTIVATE: home.feed.list"]
SurrogateFromPath: ["home", "reactivate_phase1:home.feed", "reactivate_phase1:home.feed.list", "inactivate:home.create", "inactivate:home.create.form"]
SurrogateToPath: ["home", "reactivate_phase1:home.feed", "reactivate_phase1:home.feed.list", "reactivate_phase2:home.feed", "reactivate_phase2:home.feed.list"]
I am initializing PostsController
Current state: home.feed.list, inactive states: ["home.create.form", "home.create"]
Views: (__inactives.locals: '#home' (home.create), 'form#home.create' (home.create.form)) / (root.locals) / (home.locals: '#' (home)) / (home.locals: '#' (home)) / (home.feed.locals: '#home' (home.feed)) / (home.feed.list.locals: 'list#home.feed' (home.feed.list))
The same output is generated when I am using 3 different controllers ("I am initializing.." is executed on every controller).

I have found the mistakes:
Because the second level of states (home.feed and home.create) need to be sticky as well they need a named parent ui-view. I forgot to change the single ui-view inside of the home-html into two named ui-views.
And of course I had to adapt the state definitions:
.state('home.feed', {
url: '',
views: {
'feed#home': {
template: '<div ui-view="list" ng-show="$state.includes(\'home.feed.list\')"></div>'+
'<div ui-view="view" ng-show="$state.includes(\'home.feed.view\')"></div>'+
'<div ui-view="profile" ng-show="$state.includes(\'home.feed.profile\')"></div>'
}
},
data: {
roles: ['user', 'admin']
},
sticky: true,
deepStateRedirect: {
default: { state: "home.feed.list" }
}
})
.state('home.create', {
abstract: true,
url: '',
views: {
'create#home': {
template: '<div ui-view="form" ng-show="$state.includes(\'home.create.form\')"></div>'
}
},
data: {
roles: ['user', 'admin']
},
sticky: true,
deepStateRedirect: true
})
This issue was caused by using div's with included closing tag. I changed this to the standard definition with a starting and closing tag (difference of the template definition above).

Related

How to manually focus input

I want to replicate a common item list renaming feature, where you have a list of layers and if you double click a layer, it changes the layer item to an input and that input is automatically focused with its text selected as well.
In my example, I am not able to focus() the DOM element by its ref because it says it is not defined. It only works if I click a second time on the element once its changed to an input. How do I set this autofocus?
<div v-for="(item, i) in items">
<div #click="changeToInput(i)" v-if="!item.input">{{item.name}}</div>
<input ref="input" v-model="item.name" onfocus="select()" v-else>
</div>
changeToInput(i) {
this.items[i].input = true;
//this.$refs.input.focus()
}
Here is the complete example : https://codesandbox.io/s/reverent-khayyam-2x8mp?file=/src/App.vue:481-573
Two solutions:
First one: uses v-if + this.$nextTick:
v-if will insert/destroy the component when the binding expression is true/false, so in current cycle, input hasn't been in Dom tree. You have to use nextTick to wait for next cycle to get the Dom element of Input. And this.$refs.input will be one array based on how many v-if=true, so you have to filter out the this.items to find out correct index (that is why I used one combination of Array.slice and Array.filter).
Updated: The order of the elements of this.$refs.input1 is the order VNode is created. For example: clicks input2 -> input3 -> input1, the order of this.$refs.input1 is [2, 3, 1], not [1, 2, 3].
Second one: uses v-show + this.$nextTick:
It will make things easier, because v-show only update the css styles for Dom elements, it will not add/remove component instance (Vnode) from VNode tree. So the this.$refs.input will always equal this.items.length.
new Vue ({
el:'#app',
data() {
return {
items1: [
{ name: "Joe", input: false },
{ name: "Sarah", input: false },
{ name: "Jeff", input: false }
],
items2: [
{ name: "Joe", input: false },
{ name: "Sarah", input: false },
{ name: "Jeff", input: false }
],
refSort: {}
};
},
methods: {
changeToInput1(i) {
this.items1[i].input = true;
let refCount = (this.$refs.input1 && this.$refs.input1.length) || 0
refCount < this.items1.length && (this.refSort[i] = refCount)
this.$nextTick(() => {
// the purpose uses this.refSort is record the order of this.$refs.input (Its order is same as the creating order of Ref), you can uncomment below line to see the root cause
//console.log(this.$refs.input1[0] && this.$refs.input1[0].value, this.$refs.input1[1] && this.$refs.input1[1].value, this.$refs.input1[2] && this.$refs.input1[2].value)
this.$refs.input1[this.refSort[i]].focus()
})
},
changeToInput2(i) {
this.items2[i].input = true;
this.$nextTick(() => this.$refs.input2[i].focus())
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<h3>Uses v-if: <p>{{items1}}</p></h3>
<div v-for="(item, i) in items1">
<div #click="changeToInput1(i)" v-if="!item.input">{{item.name}}</div>
<input ref="input1" v-model="item.name" onfocus="select()" v-else>
</div>
<h3>Uses v-show: <p>{{items2}}</p></h3>
<div v-for="(item, i) in items2">
<div #click="changeToInput2(i)" v-show="!item.input">{{item.name}}</div>
<input ref="input2" v-model="item.name" onfocus="select()" v-show="item.input">
</div>
</div>

Creating a dynamic Vue list that can be updated via custom props in HTML

New to VueJS. I am trying to build a custom ul component for a webpage that can be populated and updated via custom props (preferably string, but doesn't have to be), specifically in the HTML so that any other dev can simply use/update/add to the custom component with said prop, and it will add a new li through the addition of a second, third, fourth, etc. prop, appending the previous li. I am also struggling to see if more than one input type can be used on a custom prop. For a better explanation heres a coded example of what I currently have and what I would like to do:
Vue.component('resources', {
template: `
<!-- Resources Component -->
<div class="resources">
<div class="heading">
<p>Resources</p>
</div>
<ul class="resource-list">
<li v-for="item in items">
<a :src="item[source]">{{ item.message }}</a>
</li>
</ul>
</div>
`,
props: {
source: {
type: String,
default: "."
},
message: {
type: String
}
},
data () {
return {
items: [
{
message: {
type: String
},
source: {
type: String,
default: "."
}
}
]
}
}
});
And in my HTML the component looks like this:
<helpful-resources
message="test"
source="."
></helpful-resources>
This 1000% has a lot of issues, but ideally I would like to have something along the lines of this:
<helpful-resources
item: src="example url 1" message="test message 1"
item: src="example url 2" message="test message 2"
></helpful-resources>
With every addition of a new 'item' appending the previous list item with a new one with the ability to change the src and the message over and over again as needed for however many items are needed in the list.
Any help/clarification would be greatly appreciated. Thanks!
In the parent component:
<template>
<div class="resources">
<div class="heading">
<p>Resources</p>
</div>
<Helpful-resources :listItems="listItems"></Helpful-resources>
</div>
</template>
<script>
#import HelpfulResources from '#/path/to/HelpfulResources';
export default {
name: 'Resource',
components: {
HelpfulResources
},
data() {
return {
listItems: [
{src: 'link to item', message: 'special message'},
{src: 'link to item2', message: 'special message2'},
// More items ...
]
}
}
}
</script>
<style lang="scss">
/* styles */
</style>
Your component could be structured like this:
Helpful-resources.vue
<template>
<ul class="resource-list">
<li v-for="(item, index) in listItems" :key="'listItem-'+index">
<a :href="item.src">{{ item.message }}</a>
</li>
</ul>
</template>
<script>
export default {
name: 'helpful-resource',
props: [ 'listItems'],
data() {
return {
// More data ...
}
}
}
</script>
<style lang="scss">
/* styles */
</style>
Note this is styled in the vue-cli fashion, but you can modify it to fit your needs.
EDIT
To include it within an html file you would place your Vue components within the body, script tags just below the body tag.
<div id="app">
<resources :source="someData" :message="message" id="r"></resources>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.10/dist/vue.js"></script>
<script>
let resources = Vue.component('resources', {
template: `<div class="resources">
<div class="heading"><p>Resources</p></div>
<ul class="resource-list">
<li v-for="(item, index) in items" :key="index"><a :href="source">{{ item.message }}</a></li>
</ul>
</div>`,
props: {
number: Number,
source: {
type: String,
default: "."
},
message: {
type: String,
default: 'No message'
},
// Example of multiple data types
propB: [String, Number]
},
data() {
return {
items: [
{
message: this.message,
source: this.source
}
]
}
}
});
new Vue({
el: '#app',
components: {
resources
},
data: {
someData: 'path/to/source',
message: 'Special Message'
},
});
</script>
Here's a link to the fiddle anyways...Fiddle
As far as updating the list goes, you could use an API call to get data asynchronously or allow users to add info via button or input and use a method. Or if you are talking strictly hardcoding extra values, other developers would add to your file...
Hopefully this helps. If not, please clarify.

Bootstrap popover contents coming but not showing in html

I am having trouble in showing popover.
Scenerio:
I want to show popover on hover, and popeover's content comes from ajax request.
Coffeescript:
$('.preview').bind 'mouseenter', ->
that = #
unless $(#).data('original-title')
$.ajax
type: "get"
url: $(that).data('url')
success: (data) ->
$(that).data('content', data)
$(that).popover({
html: true
delay: { show: 100, hide: 360000 }
trigger: 'hover'
content: data
template: '<div class="popover preview-popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
})
this code generate a small popover .... which have contents(ajax loaded html) but not visible

How do I dynamically change ng-grid table size when parent containing div size changes?

I am changing the size of the containing div using ng-class and an expression that evaluates whether or not an edit form is displayed. If the edit form is displayed I want to change the size of the div containing the ng-grid and the ng-grid itself.
<div class=" row-fluid">
<div ng-class="{'span7' : displayEditForm == true, 'span12': displayEditForm == false}" >
<ul class="nav nav-tabs" style="margin-bottom: 5px;">
<li class="active">Activities Needing My Approval</li>
<li>My Activities Needing Approval </li>
<li>My Activities</li>
</ul>
<div class="edus-admin-manage-grid span12" style="margin-left:0;" ng-grid="gridOptions"></div>
</div>
<div class="span5" ng-show="displayEditForm">
<div class="edus-activity-container">
<div class="edus-admin-activities-grid">
<div ng-include="'/partials/' + activity.object.objectType + '.html'" class="edus-activity"></div>
<!-- <div ng-include="'/partials/admin-activity-actions.html'"></div>-->
</div>
</div>
<div ng-include="'/partials/admin-edit-activity-grid-form.html'"></div>
</div>
</div>
The div containing the navbar and grid changes size via ng-class (from span12 to span7), but the ng-grid does not refresh. How can I trigger the refresh of ng-grid given the change in the parent div?
I've included my gridOptions below:
$scope.gridOptions = {
plugins: [gridLayoutPlugin],
data : 'activities',
showFilter: true,
/* enablePaging: true,*/
showColumnMenu: true,
/* showFooter: true,*/
rowHeight: 70,
enableColumnResize: true,
multiSelect: false,
selectedItems: $scope.selectedActivities,
afterSelectionChange: function(rowItem,event){
if($scope.selectedActivities && $scope.selectedActivities.length > 0){
$scope.activity = $scope.selectedActivities[0];
$scope.activityViewState.index = $scope.activities.indexOf($scope.activity);
$scope.displayEditForm = true;
console.log("DEBUG :::::::::::::::: updated displayEditForm.", $scope.displayEditForm);
if($scope.activity.ucdEdusMeta.startDate) {
// $scope.activity.ucdEdusMeta.startDate = new Date($scope.activity.ucdEdusMeta.startDate);
$scope.edit.startDate = moment($scope.activity.ucdEdusMeta.startDate).format("MM/DD/YYYY");
$scope.edit.startTime = moment($scope.activity.ucdEdusMeta.startDate).format("hh:mm A");
}
if($scope.activity.ucdEdusMeta.endDate) {
// $scope.activity.ucdEdusMeta.endDate = new Date($scope.activity.ucdEdusMeta.endDate);
$scope.edit.endDate = moment($scope.activity.ucdEdusMeta.endDate).format("MM/DD/YYYY");
$scope.edit.endTime = moment($scope.activity.ucdEdusMeta.endDate).format("hh:mm A");
}
}
},
/* pagingOptions: { pageSizes: [5, 10, 20], pageSize: 10, totalServerItems: 0, currentPage: 1 },*/
columnDefs: [
{field: 'title', displayName: 'Title', width:'15%',
cellTemplate: '<div class="ngCellText", style="white-space: normal;">{{row.getProperty(col.field)}}</div>'},
{field: 'actor.displayName', displayName: 'DisplayName', width:'10%',
cellTemplate: '<div class="ngCellText", style="white-space: normal;">{{row.getProperty(col.field)}}</div>'},
{field: 'object.content', displayName:'Content', width:'35%',
cellTemplate: '<div class="ngCellText", style="white-space: normal;">{{row.getProperty(col.field)}}</div>'},
{field: 'ucdEdusMeta.startDate', displayName: 'Start Date', width:'20%',
cellTemplate: '<div class="ngCellText" ng-class="col.colIndex()"><span ng-cell-text>{{row.getProperty(col.field) | date:"short"}} </span></div>'},
{field: 'ucdEdusMeta.endDate', displayName: 'End Date', width:'20%',
cellTemplate: '<div class="ngCellText" ng-class="col.colIndex()"><span ng-cell-text>{{row.getProperty(col.field) | date:"short"}} </span></div>'}
// {field: '', displayName: ''},
]
};
Here's the CSS used by the grid:
.edus-admin-manage-grid {
border: 1px solid rgb(212,212,212);
width: 100%;
height: 700px
}
You can use ng-grid's layout plugin (ng-grid-layout.js). It should come with ngGrid located at:
ng-grid/plugins/ng-grid-layout.js
(UPDATED: now at https://github.com/angular-ui/ng-grid/blob/2.x/plugins/ng-grid-layout.js)
You will have to include an additional script tag pointing to this js file in your main index.html file. And the order of including this versus ng-grid.js is important.
You would have to set a watch on displayEditForm and then call the plugin's updateGridLayout() function.
So it would be something like:
var gridLayoutPlugin = new ngGridLayoutPlugin();
// include this plugin with your grid options
$scope.gridOptions = {
// your options and:
plugins: [gridLayoutPlugin]
};
// make sure grid redraws itself whenever
// the variable that ng-class uses has changed:
$scope.$watch('displayEditForm', function() {
gridLayoutPlugin.updateGridLayout();
});
From my understanding, watches generally belong in the link function rather than the controller but it will work in either spot. You could also go a bit further and say:
$scope.$watch('displayEditForm', function(newVal, oldVal) {
if (newVal !== undefined && newVal !== oldVal) {
gridLayoutPlugin.updateGridLayout();
}
});
To make sure this only fires when the data switches from true/false. This would matter if your data is initially undefined and you waste time calling grid redraw before you give it an initial value.

List data from json on Sencha

i am trying to show data from webserver json and it is not shown in view. At first i tried to show message only but could not succed. following this link and sample provided by #rdougan i have been able to parse upto categories but unable parse latter value can any one guide me.
code i have done till now are as follow:
{
xtype: 'panel',
padding: '20px',
//height: 100,
styleHtmlContent:true,
store: 'TodaysWordStore',
models: 'TodaysWordModel',
tpl:
[
'<tpl for=".">',
'<ul class="parabox">',
'<li><h2 onclick = "ratingStar()">{message} </h2> ',
'<ul class="para-box-wrapper">',
'<li class="greenbox"><div class="paragraph-def">',
'{dictionaryDef}',
'<span class="authorBox">Author: {authorName}</span>',
'</div><div class="starBox">',
'{ratingBox}',
'</div></li>',
'</ul></li></ul>',
'</tpl>',
]
// '<div><strong>{dictionaryWord}</strong>, {dictionaryDef} <em class="muted">({role})</em></div></tpl>']
},
My store is
Ext.define('appname.store.TodaysWordStore',{
extend: 'Ext.data.Store',
config:
{
model: 'appname.model.TodaysWordModel',
autoLoad:true,
proxy:
{
type: 'ajax',
url : 'location' //Your file containing json data
reader:
{
type: 'json',
//rootProperty:'accountInfo'
}
}
}
});
My model is
Ext.define('appname.model.TodaysWordModel', {
extend: 'Ext.data.Model',
config: {
fields:[
{name: 'message', mapping: 'accountInfo.message'}
]
}
});
The json i want to parse
({"accountInfo":
{"expire_date":"2014-07-02 08:01:09",
"subscribe_date":"2013-07-02 08:01:09",
"time_remain":" 358 Days 1 Hour 24 Minutes",
"status":"not expired"},
"status":"TRUE",
"message":"Todays Word",
"data":
[{"name":"curtain",
"author":"admin",
"word_id":"35",
"category":"business",
"definition":
[{"rating":
{"red":"0",
"green":"1",
"yellow":"0",
"total_rating":1,
"final_rating":"Green"},
"defintion":"curtain",
"def_id":"11",
"example":"This is a sample example.",
"author":"admin"},
{"rating":
{"red":0,
"green":0,
"yellow":0,
"total_rating":0,
"final_rating":"This definition is not rated yet."},
"defintion":"to cover something",
"def_id":null,
"example":"This is curtain example.",
"author":"admin"}],
"is_favourite":"No"}]})
Help me
You are trying to use a panel, when you should be using a dataview. The tpl is meant to be used with the data config option when using an Ext.panel.Panel, where data is just an Object containing the data to be used by the tpl. However, when you are using a store for your data, you should use an Ext.view.view instead of the panel. See the docs here: http://docs.sencha.com/extjs/4.2.1/#!/api/Ext.view.View
I solved it by adding an extra itemTpl. Since i had tpl for data only it was not able to fetch data from definitions node.
{
xtype: 'list',
store: 'ghak',
height: 140,
layout: 'fit',
scrollable: {
direction: 'vertical',
directionLock: true,
},
margin: '0 0 5px 0',
itemTpl: [
'<div>',
'<tpl for="data">',
'<ul class="parabox">',
'<li><h2 class="noStar" onclick = "addtofavourite({word_id})">{name} </h2>',
'<tpl for="definitions">',
'<ul class="para-box-wrapper">',
'<li class="{rating}"><div class="paragraph-def" onclick = "rating({def_id})">','{defintion}',
'<span class="authorBox">Author: {author}</span>',
'</li>' ,
'</div>',
'</ul></li>',
'</tpl>',
'</ul>',
'</tpl>',
'</div>'
].join('')
Hope it would help someone.
Thanks.