Durandal : ViewModel does not work in a common view - durandal

I'm trying to create a common area for my navigation/menus (vertical) in my shell page. This shell's menu area will then be updated by new set of menus on every page transition. heres inside my shell.js
define(function (require) {
var router = require('durandal/plugins/router');
var Menu = function (name, children) {
this.name = ko.observable(name);
this.children = ko.observableArray(children);
}
var Menus = ko.observableArray([]);
function GetDSRList() {
router.navigateTo('#/reports/list2');
}
function activate() {
router.mapNav('reports/index', 'viewmodels/reports/index', 'Reports');
router.mapNav('reports/list', 'viewmodels/reports/list', 'List');
router.mapNav('reports/list2', 'viewmodels/reports/list2', 'List2');
router.mapNav('home/menu', 'viewmodels/home/menu', 'Main');
return router.activate('home/menu');
}
function viewAttached() {
$(document).ready(function () {
$("#panelbar").kendoPanelBar({
expandMode: "single"
}),
$("#splitter").kendoSplitter({
panes: [
{ collapsible: false, resizable: false, size: "20%" },
{ collapsible: false }
]
});
});
}
function token() {
return $("input[name='__RequestVerificationToken']").val();
}
var shell = {
router: router,
activate: activate,
viewAttached: viewAttached,
GetDSRList: GetDSRList,
Menus: Menus,
token: token,
Menu: Menu
}
return shell;
And for the View (shell.html)
<div style="height:100%;">
<div class="loader" data-bind="css: { active: router.isNavigating }">
</div>
<div id="splitter" style="border:0;height:100%">
<div class="nav-panel-section">
<ul id="panelbar">
<li data-bind="foreach : Menus">
<span class="k-link k-header" data-bind="text: name">
<span class="k-icon k-i-arrow-s k-panelbar-expand"></span>
</span>
<ul data-bind="foreach: children">
<li>
<span class="k-link" data-bind="text: $data,click:this.GetDSRList"></span>
</li>
</ul>
</li>
</ul>
</div>
Log off
<div id ="partialContent">
<div class="container-fluid page-host">
<!--ko compose: {
model: router.activeItem, //wiring the router
afterCompose: router.afterCompose, //wiring the router
cacheViews:true //telling composition to keep views in the dom, and reuse them (only a good idea with singleton view models)
}--><!--/ko-->
<form method="post" action="Account/LogOff" id="logoutForm" >
<input type="hidden" name="__RequestVerificationToken" data-bind="value:token"/>
</form>
</div>
</div>
</div>
</div>
Then going to the url '#/reports/list2' i have this viewmodel (list2.js) :
define(function (require) {
var shell = require('viewmodels/home/shell')
function activate() {
shell.Menus = [];
shell.Menus = [
new shell.Menu("Menu3", ["A", "B", "C"]),
new shell.Menu("Menu5", ["A", "B", "C"])
];
}
var list2 = {
activate : activate
}
return list2;
});
But the shell's menu UI does not change unless you refresh the entire page. Am I missing something here? Thnx

Your problem is that you create an observableArray and then wipe it out on the new view model load. You need to use the setter function for your observableArray to maintain it's connection to the DOM (please don't correct my grammar on that one, trying to KISS!!)
function activate() {
shell.Menus = ([]);
shell.Menus = ([
new shell.Menu("Menu3", ["A", "B", "C"]),
new shell.Menu("Menu5", ["A", "B", "C"])
]);
}
Simply adding the parans should fix your issue. When you are clearing use the parans, when you are setting use the parans.

Related

vuejs event modifier search list process

I'm a vue js newbie, I perform a get operation with the value entered in the search input and if there is a result, I show it in "listShow", if there is no result, I return "listShow" false. no problem so far. only if the user chooses any of the incoming data, I send the "name" searchtext of the incoming data to the input. but if there is no result "listShow false" and click somewhere outside the input
I want to make "newDiv" true. so "inputOutClick" does the job, but when I click on any of the "search" data, "inputOutClick" does not allow this "selecteds()" function to fire.
And also, is my coding style correct, I'm getting too repetitive.
Is it ok to use search #keyup?
Does it make sense to use v-on:focusout?
const app = new Vue({
el: '#app',
data: {
searchText: '',
listShow: true,
newDiv:false,
searcList:[],
},
methods: {
inputOutClick() {
this.listShow = false
},
selecteds(list) {
this.listShow = false;
this.searchText = list.name;
},
async search() {
if (this.searchText !== '') {
const res = await this.callApi('get', 'search' + '?filter=' + this.searchText)
if (res.status === 200) {
this.searcList = this.getList;
if (res.data.length > 0) {
this.listShow = true;
} else {
this.listShow = false;
}
}
} else {
this.listShow = false;
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="app">
<div>
<input
type="text"
v-model="searchText"
#keyup="search"
v-on:focusout="inputOutClick"
/>
<div v-if="listShow" style="background:red">
<ul>
<li v-for="(list, index) in searcList">
<a #click="selecteds(list)">{{ list.name }}</a>
</li>
</ul>
</div>
<div v-if="newDiv">
<p>hello</p>
</div>
</div>
</div>
You can use #mousedown.prevent on the searchList entries (where the click handler is attached). This prevents the v-on:focusout event being fired, if a searchList entry is clicked.
<input
type="text"
v-model="searchText"
#keyup="search"
v-on:focusout="inputOutClick"
/>
<a
#click="selectEntry(entry)"
#mousedown.prevent
>
xxx
</a>
Use #mousedown instead of #click.
=> #click runs after #focusout.
=> #mousedown runs before #focusout.
If you do not want to run the focusout function on the input field when the list is clicked at all then you can use #mousedown.prevent="selecteds(list)".
See example below (click on "Full page" so the console.log doesn't block the list):
const app = new Vue({
el: '#app',
data: {
searchText: '',
listShow: true,
newDiv:false,
searcList:[],
list: {}
},
methods: {
inputOutClick() {
console.log("inputOutClick");
if (this.listShow == false) {
console.log("mousedown was fired first");
}
this.listShow = false
},
selecteds(list) {
console.log("selecteds");
this.listShow = false;
this.searchText = list.name;
},
async search() {
console.log("search");
this.listShow = true;
this.searcList = ['aeaeg', 'tdthtdht', 'srgsr'];
this.list.name = "TEST"
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="app">
<div>
<input
type="text"
v-model="searchText"
#keyup="search"
v-on:focusout="inputOutClick"
/>
<div v-if="listShow" style="background:red">
<ul>
<li v-for="(list, index) in searcList">
<a #mousedown="selecteds(list)">LIST TEXT</a>
</li>
</ul>
</div>
<div v-if="newDiv">
<p>hello</p>
</div>
</div>
</div>

How to toggle individual row at a time in vue for rows generated from an array? [duplicate]

I work with single file components and have a list in one of them. This list should work like a accordion, but as far as I can find in the Vuejs docs, it's not that easy to make each item open separately very easily. The data (questions and answers) is retrieved from an ajax call. I use jQuery for that, but would like to know how I can make the accordion work Vuejs-style. Any help would be appreciated!
Here's the code:
export default {
name: 'faq-component',
props: ['faqid', 'faqserviceurl', 'ctx'],
data: function () {
return {
showFaq: "",
totalFaqs: this.data,
isOpen: true
}
},
watch: {
'showFaq': function(val, faqid, faqserviceurl) {
var self = this;
$.ajax ({
url: this.faqserviceurl,
type: 'GET',
data: {id: this.faqid, q: val, scope:1},
success: function (data) {
self.totalFaqs = data;
},
error: function () {
$("#answer").html('Sorry');
}
});
}
},
methods: {
'toggle': function() {
this.isOpen = !this.isOpen
}
}
}
<template>
<div class="card faq-block">
<div class="card-block">
<form>
<div class="form-group">
<input class="form-control" type="text" placeholder="Your question" id="faq" v-model="showFaq">
</div>
</form>
<div id="answer"></div>
<ul class="faq">
<li v-for="faq in totalFaqs">
<p class="question" v-html="faq.vraag" v-bind:class={open:isOpen} #click="isOpen = !isOpen"></p>
<p class="answer" v-html="faq.antwoord"></p>
</li>
</ul>
</div>
</div>
</template>
Add an isOpen property to each object in totalFaqs and use that instead of your single isOpen property in data.
<p class="question" v-html="faq.vraag" v-bind:class={open: faq.isOpen} #click="faq.isOpen = !faq.isOpen"></p>
If you can't change the model from the server side, then add it client side.
success: function (data) {
data.forEach(d => self.$set(d, 'isOpen', false))
self.totalFaqs = data
}

Vue: How to switch between displaying input and label with v-if

I need to be able to switch between an input field and a label. When the button "Add Location" is clicked (which create a new div), the input field must be visible. But when the div "Expandable" is maximized it must be hidden and the label visible instead!
The input field should only be visible right after the mentioned button is clicked, else the label has to take its place. What is the best way to achieve this? I was thinking about using some sort of toggle since I am using that in other places.
The label and the input field is placed in the div class "switch".
You can also see the code in this jsFiddle!
Html
<div id="lotsOfDivs">
<addingdivs></addingdivs>
</div>
Vue
var gate = 0;
Vue.component('addingdivs', {
template: `
<div>
<div id="header">
<button class="addDiv" type="button" #click="createDiv">ADD LOCATION</button>
</div>
<div class="parent" v-for="div in divs" :style=" div.height ? { 'height': div.height }: null">
<div class="big" v-if="div.expanded" :key="'expanded' + div.id">
<div class="switch">
<input type="text" v-if="inputFieldInfo">
<label class="propertyLabel" v-else>
<div class="firstChild">
<button class="done" #click="increaseLimit">INCREASE</button>
</div>
<div class="secondChild">
<button class="done" #click="expand(div)">EXPAND</button>
</div>
</div>
<div class="small" v-else :key="'collapsed' + div.id">
<button class="done" #click="expand(div)">EXPAND</button>
</div>
</div>
</div>
`,
data: function() {
return {
gate: gate,
height: "",
count: 0,
locationsArr: ["one", "two", "three"],
divs: [],
InputFieldInfo: false
}
},
methods: {
expand: function(div) {
if (div.expanded) {
div.expanded = false
this.height = ''
} else {
div.expanded = true
this.height = '7vh'
}
},
createDiv: function() {
if (this.count <= gate) { // Here you can decide how many divs that will be generated
// this.count++;
this.divs.push({
id: this.count,
expanded: true,
inputFieldInfo: true,
height: '',
});
this.count++
}},
increaseLimit: function() {
// Here you can increase the number of divs that it's possible to generate
gate++;
}
}
});
new Vue({
el: '#lotsOfDivs',
});
The template had a few compilation errors:
The <label> needs a closing tag (and text content to be useful)
The <div class="big"> needs a closing tag
The v-if was bound to inputFieldInfo, but that variable was declared as InputFieldInfo (note the uppercase I), but based on your behavior description, this field should be unique per location container, so a single data property like this wouldn't work (if I understood your description correctly).
Each location container should have a variable to contain the location name (e.g., locationName) and another variable to contain the show/hide Boolean for the <input> and <label> (i.e., inputFieldInfo):
createDiv: function() {
this.divs.push({
// ...
inputFieldInfo: true,
locationName: ''
});
}
Then, we could bind div.inputFieldInfo and div.locationName to the <input>. We bind to v-model so that the user's text is automatically reflected to the div.locationName variable:
<input v-if="div.inputFieldInfo" v-model="div.locationName">
The <label>'s content should be div.locationName so that it contains the text from the <input> when shown:
<label class="propertyLabel" v-else>{{div.locationName}}</label>
To switch the <input> with the <label> when the expand-button is clicked, we update expand() to set div.inputFieldInfo to false but only when div.locationName is not empty (this gives the user a chance to revisit/re-expand the container to fill in the location later if needed):
expand: function(div) {
if (div.expanded) {
div.expanded = false
if (div.locationName) {
div.inputFieldInfo = false
}
// ...
updated jsfiddle
You had some missing closing tags and an error with InputFieldInfo, it should have a lowercase i.
var gate = 0;
Vue.component('addingdivs', {
template: `
<div>
<div id="header">
<button class="addDiv" type="button" #click="createDiv">ADD LOCATION</button>
</div>
<div class="parent" v-for="div in divs" :style=" div.height ? { 'height': div.height }: null">
<div class="big" v-if="div.expanded" :key="'expanded' + div.id">
<div class="switch">
<input type="text" v-if="inputFieldInfo">
<label class="propertyLabel" v-else>Label</label>
<div class="firstChild">
<button class="done" #click="increaseLimit">INCREASE</button>
</div>
<div class="secondChild">
<button class="done" #click="expand(div)">EXPAND</button>
</div>
</div>
</div>
<div class="small" v-else :key="'collapsed' + div.id">
<button class="done" #click="expand(div)">EXPAND</button>
</div>
</div>
</div>
`,
data: function() {
return {
gate: gate,
height: "",
count: 0,
locationsArr: ["one", "two", "three"],
divs: [],
inputFieldInfo: true
}
},
methods: {
expand: function(div) {
this.inputFieldInfo = false
if (div.expanded) {
div.expanded = false
this.height = ''
} else {
div.expanded = true
this.height = '7vh'
}
},
createDiv: function() {
this.inputFieldInfo = true
if (this.count <= gate) { // Here you can decide how many divs that will be generated
// this.count++;
this.divs.push({
id: this.count,
expanded: true,
inputFieldInfo: true,
height: '',
});
this.count++
}
},
increaseLimit: function() {
// Here you can increase the number of divs that it's possible to generate
gate++;
}
}
});
new Vue({
el: '#lotsOfDivs',
});
You just basically toggle the inputFieldInfo data, whenever each button is pressed.
You can do that by using toggle variable like this
Vue.component('addingdivs', {
template: `
<div>
<div>
<input type="text" v-if="takeinput">
<label v-if="!takeinput">
<button #click="toggleInput()">
</div>
</div>
`,
data: function() {
return {
takeinput:true,
}
},
methods: {
toggleInput: function(){
let vm = this;
vm.takeinput = ( vm.takeinput == true) ? false : true
}
}
});
new Vue({
el: '#lotsOfDivs',
});
In this example, we are just toggeling value of takeinput on click , so according the value either label or input will be showed.
This is very basic exmpale. But you can extend it as your need

Vuejs open/toggle single item

I work with single file components and have a list in one of them. This list should work like a accordion, but as far as I can find in the Vuejs docs, it's not that easy to make each item open separately very easily. The data (questions and answers) is retrieved from an ajax call. I use jQuery for that, but would like to know how I can make the accordion work Vuejs-style. Any help would be appreciated!
Here's the code:
export default {
name: 'faq-component',
props: ['faqid', 'faqserviceurl', 'ctx'],
data: function () {
return {
showFaq: "",
totalFaqs: this.data,
isOpen: true
}
},
watch: {
'showFaq': function(val, faqid, faqserviceurl) {
var self = this;
$.ajax ({
url: this.faqserviceurl,
type: 'GET',
data: {id: this.faqid, q: val, scope:1},
success: function (data) {
self.totalFaqs = data;
},
error: function () {
$("#answer").html('Sorry');
}
});
}
},
methods: {
'toggle': function() {
this.isOpen = !this.isOpen
}
}
}
<template>
<div class="card faq-block">
<div class="card-block">
<form>
<div class="form-group">
<input class="form-control" type="text" placeholder="Your question" id="faq" v-model="showFaq">
</div>
</form>
<div id="answer"></div>
<ul class="faq">
<li v-for="faq in totalFaqs">
<p class="question" v-html="faq.vraag" v-bind:class={open:isOpen} #click="isOpen = !isOpen"></p>
<p class="answer" v-html="faq.antwoord"></p>
</li>
</ul>
</div>
</div>
</template>
Add an isOpen property to each object in totalFaqs and use that instead of your single isOpen property in data.
<p class="question" v-html="faq.vraag" v-bind:class={open: faq.isOpen} #click="faq.isOpen = !faq.isOpen"></p>
If you can't change the model from the server side, then add it client side.
success: function (data) {
data.forEach(d => self.$set(d, 'isOpen', false))
self.totalFaqs = data
}

displaying models on different div ember.js

In Ember.js, what is the best way to display a models properties?
Say my model is 'products' so I have a product list, when I click on an item on that list I want the details displayed in another div and not override that view.
How can I do this, below if my code.
<script type="text/x-handlebars">
{{ view App.ListReleasesView }}
</script>
<script type="text/x-handlebars">
{{ view App.ReleaseDataView }}
</script>
App.ListReleasesView = Ember.View.extend({
templateName: 'app/templates/releases/list',
releasesBinding: 'App.releasesController',
showNew: function() {
this.set('isNewVisible', true);
},
hideNew: function() {
this.set('isNewVisible', false);
},
refreshListing: function() {
App.releasesController.findAll();
}
});
App.selectedReleaseController = Ember.Object.create({
release: null
});
list template:
<ul>
{{#each releases}}
{{view App.ShowReleaseView releaseBinding="this"}}
{{/each}}
</ul>
{{#if isNewVisible}}
{{view App.NewReleaseView}}
{{/if}}
<div class="commands">
<a href="#" {{action "showNew"}}>New Release</a>
<a href="#" {{action "refreshListing"}}>Refresh Listing</a>
</div>
App.ShowReleaseView = Ember.View.extend({
templateName: 'app/templates/releases/show',
//classNames: ['show-release'],
classNameBindings: ['isSelected'],
// tagName: 'tr',
doubleClick: function() {
// this.showEdit();
// this.showRelease();
var release = this.get("release");
App.selectedReleaseController.set("release", release);
},
isSelected: function(){
var selectedItem = App.selectedReleaseController.get("release");
release = this.get("content");
if (release === selectedItem) {return true; }
}.property('App.selectedReleaseController.release')
show template:
{{#if App.selectedReleaseController.release}}
{{view App.ReleaseDataView}}
{{else}}
{{release.name}}
{{/if}}
App.ReleaseDataView = Ember.View.extend({
templateName: 'app/templates/releases/releaseData',
releaseBinding: 'App.selectedReleaseController.release',
// classNames: ['release'],
});
release template:
{{#if isSelected}}
<div class="name">
{{editable "release.name"}} {{editable "release.description"}}
</div>
{{/if}}
You'll want to have a simple controller whose job will be managing the selection state.
App.selectedReleaseController = Ember.Object.create({
content: null
});
Then you'll have another view, for the details, which is bound to that controller.
{{view App.ReleaseDetailsView releaseBinding="App.selectedReleaseController.content"}}