I am facing a really annoying behaviour of grouped WinJS.Binding.List.
When trying to create a SemanticZoom, i fill (in markup) the zoomed-out view items and zoomed-in view headers with the same data from a list.groups.dataSource :
<div id="semanticZoomDiv" data-win-control="WinJS.UI.SemanticZoom" data-win-options="{ initiallyZoomedOut: true }">
<div id="zoomedInListView"
data-win-control="WinJS.UI.ListView"
data-win-options="{
itemDataSource: Data.myCategories.dataSource,
itemTemplate: select('#zoomedInItemTemplate'),
groupDataSource: Data.myCategories.groups.dataSource,
groupHeaderTemplate: select('#zoomedInHeaderTemplate')
}"
></div>
<div id="zoomedOutListView"
data-win-control="WinJS.UI.ListView"
data-win-options="{
itemDataSource: Data.myCategories.groups.dataSource,
itemTemplate: select('#zoomedOutItemTemplate')
}"
></div>
</div>
This code works or doesn't depending on my data :
if i use my real-world data, which is a 3-level tree of categories generated from remote data (i call WinJS.xhr), the zoomed-out ListView fails to bind its itemDataSource with the groups. So my SemanticZoom is well processed but the zoomed-out view is empty.
More precisely, in my zoomed-out view :
This code fails :
data-win-options="{
itemDataSource: Data.myCategories.groups.dataSource,
itemTemplate: select('#zoomedOutItemTemplate')
}"
This code works but displays the detailed categories while i want the groups :
data-win-options="{
itemDataSource: Data.myCategories.dataSource,
itemTemplate: select('#zoomedOutItemTemplate')
}"
This code works, which proves my groups are well-formed when i call createGrouped earlier, but it makes no sense for me to do this since i need my zoomed-out view to display only main groups in which to zoom :
data-win-options="{
itemDataSource: Data.myCategories.dataSource,
itemTemplate: select('#zoomedOutItemTemplate'),
groupDataSource: Data.myCategories.groups.dataSource
}"
if i use sample and simpler data, everything works fine, and i can use my SemanticZoom.
Since the bug seems to happen depending on my input data, below are two snippets of the raw data with which i define a WinJS.Binding.List :
The real-world data, not working : (json-stringified from my generated data)
var myCategories = [{
"id":"CAT_1_1",
"title":"Category 1.1",
"groupKey":0,
"groupId":"CAT_1",
"groupTitle":"Category 1" },
{...}];
The sample data, working : (hand-written)
var myCategories = [
{ group: 1, id: "1.1", title: "item 1.1" },
{ group: 1, id: "1.2", title: "item 1.2" },
];
FYI :
- the bug doesn't come from the templates, i checked.
Thanks for your help, i have struggled with this one for 2 days now...
I finally found the problem :
WinJS.Binding.List.createGrouped() must receive a groupKey(itemData) function as parameter, which returns a key to identify the group for each item.
The returned key must be a STRING, while my real data was returning an INT. Changing this solved the problem.
This is not mentioned in common Windows 8 quickstarts and confusion may come from the compare(leftKey, rightKey) function which uses group keys to calculate a sorting criteria and returns a number.
Hope this will help.
Related
let's say I have a very simple setup using data() and a function written in the methods part. With the function I'm returning a random number between 1 and 3, while the data contains an list of blog elements.
When displaying the result from randomNumber directly on the page using <p>{{randomNumber()}}</p> the value (1-3) get's printed. In my case I'm printing all posts from blogs using v-for on the parent div.
When however, trying to alter a css class with said number the number does not get printed. For this particular test I have made .g1, .g2 and .g3 with different style attributes. When inspecting the source code directly in the browser the .blog-thumb class has g{{randomNumber()}} as a second class.
i tried to call randomNumber() within the blogs item but I'm still unable to add the generated class to the css class section.
Thank you for your help or suggestions.
HTML:
<div class="col" v-for="blog in blogs" :key="blog.id">
<div class="blog-thumb g{{randomNumber()}}"> <!-- DOES NOT GET PRINTED -->
{{randomNumber()}} <!-- GETS PRINTED -->
<p class="date">{{ blog.date }}</p>
</div>
<h2>{{blog.title}}</h2>
<p>{{blog.content}}</p>
</div>
Vue:
methods : {
randomNumber : function(){
return Math.floor(Math.random() * 3+1);
}
},
data() {
return{
blogs: [
{
id: '1',
title: 'Amet consectetur adipisicing',
date: 'March 3rd 2022',
content: 'Lorem ipsum...'
},
{...}
]
}
}
after fiddling with it for quite a while today I found this solution:
I changed the function to return a complete classname (in this case, g with a random number between 1 and 3)
randomNumber : function(){
return 'g' + Math.floor(Math.random() * 3+1);
}
in the html part I added the v-bind to the class and passed the function (v-bind:class="randomNumber()")
<div class="col" v-for="blog in blogs" :key="blog.id">
<div class="blog-thumb" v-bind:class="randomNumber()">
<p class="date">{{ blog.date }}</p>
</div>
<h2>{{blog.title}}</h2>
<p>{{blog.content}}</p>
</div>
with this, now every col blog-thumb class got another generated class added to it.
The goal:
generate form fields from JSON/CMS
have a param in the JSON that allows two fields to sit next to each other on a single line
The solution so far:
I’m using Vue Formulate's schema API to generate fields. In Vue Formulate's options, I can conditionally add a class to the outer container based on a parameter in the context.
classes: {
outer(context, classes) {
if (context.attrs.colspan === 1) {
return classes.concat('col-span-1')
}
return classes.concat('col-span-2')
},
I’m using Tailwind, which requires no classname concatenation and actually want the default to be col-span-2, so if you’re inclined to copy this, your logic may vary.
With a few classes applied to the FormulateForm, this works really well. No additional wrapper rows required thanks to CSS grid:
<FormulateForm
v-model="values"
class="sm:grid sm:grid-cols-2 sm:gap-2"
:schema="schema"
/>
The schema now looks something like this:
[
{
type: 'text',
name: 'first_name',
label: 'First name',
validation: 'required',
required: true,
colspan: 1,
},
The problem/question
Vue Formulate’s schema API passes all attributes defined (other than some reserved names) down to the input element. In my case, that results in:
<div
data-classification="text"
data-type="text"
class="formulate-input col-span-1"
data-has-errors="true"
>
<div class="formulate-input-wrapper">
<label
for="formulate-global-1"
class="formulate-input-label formulate-input-label--before"
>
First name
</label>
<div
data-type="text"
class="formulate-input-element formulate-input-element--text"
>
<input
type="text"
required="required"
colspan="1" <--------------- hmm…
id="formulate-global-1"
name="first_name"
>
</div>
</div>
</div>
I recognize that I can name my attribute data-colspan so that I’m not placing a td attribute on an input, but I think of colspan as metadata that I don’t want applied to the template. Is there a way to prevent this from being applied to the input—perhaps a reserved word in the schema API that allows an object of metadata to be accessed via context without getting applied to v-bind="$attrs"?
The vue-formulate team helped me out on this one. Very grateful. Much love.
There is a way to prevent it from landing on the input, and that's to use the reserved outer-class property in the schema:
[
{
type: 'text',
name: 'first_name',
label: 'First name',
validation: 'required',
required: true,
'outer-class': ['col-span-1'],
},
This means that I don't need to do this at all:
classes: {
outer(context, classes) {
if (context.attrs.colspan === 1) {
return classes.concat('col-span-1')
}
return classes.concat('col-span-2')
},
vue-formulate supports replacing or concatenating classes via props. I managed to overlook it because I didn't recognize that everything you pass into the schema API is ultimately the same as applying a prop of that name.
Classes can be applied to several other parts of the component as well—not just the outer/container. More information here:
https://vueformulate.com/guide/theming/customizing-classes/#changing-classes-with-props
I am building a simple quiz with vuejs. I've been working in https://codepen.io/jasonflaherty/pen/NBaJLO on this. I am able to get the correct responses to the checked input, however, it is not scoped to this.input and cascades to all radio buttons. I tried to use bind class with:
<span class="" v-bind:class="{ 'badge badge-success' : isCorrect, 'badge badge-danger' : isWrong, 'showthis' : showIt }">{{response.correct}}</span>
and am currently using:
v-if="isCorrect"
v-if="isWrong"
However, it is the same issue with the scope bound to the same input vs all of them.
What am I missing in vue to make this distinction?
You need to track at the question level rather than the global level.
Here are a couple simple changes you can make. Notice the input changes for value and v-model and also the conditions for the correct/wrong spans. This creates a property, selection on each question to track the currently selected answer for this specific question.
<li v-for="response in question.responses">
<label class="form-check-label">
<input class="form-check-input" type="radio"
:key="index"
:name="question.group"
:value="response.text"
v-model="question.selection">
{{response.text}}
<span v-if="question.selection === response.text && response.correct ==='Correct!' ">
Correct
</span>
<span v-else-if="question.selection === response.text && response.correct !=='Correct!' ">
Wrong
</span>
</label>
</li>
You could clean it up some more by altering your data model. There is no reason to save the correct text in the data. You can simply have the answer as a property and determine the textual response based on the selection.
var quizquestions = {
questions: [
{
text: "Subtract 219 from 500.",
group: "qone",
responses: [281, 719, 218, -219],
answer: 281, // correct answer
selection: null, // for v-model to track selected
},
]
};
I'm trying to find a way to bind array of objects within Vue select-element. The case is somewhat as follows:
data: {
ideas: [
{ id: 1, code: "01A", text: "option 1", props: [] },
{ id: 2, code: "02A", text: "option 2 , props: [{ details: "something" }]}
]},
currentForm: {
something: "foo",
else: "bar",
ideaCode: "01A",
text: "option 1"
}
];
... and in HTML ...
<select v-model="currentForm.ideaCode" #change="setCodeAndLabelForForm(???)">
<option v-for="i in ideas" value="i">{{ i.text }}<option>
</select>
Basically I need to be able to track which object user selects, trigger my own change-event, all the while having binding with a single key from another object... selected value / reference-key should be separated from user-selected option/object. Note: currentForm is not same object-type as option! It only contains some of those properties which option happens to have, and which I'm trying to transfer to options by triggering change-event for user-selection.
The problem is I haven't figured out how to pass currently selected value for the function OR how to write something like:
<select v-model="selectedIdea" #change="setCodeAndLabelForForm" :track-by="currentForm.ideaCode">
<option v-for="i in ideas" value="i">{{ i.text }}<option>
</select>
One possible (and working) approach is:
<select v-model="currentForm.ideaCode" #change="setCodeAndLabelForForm">
<option v-for="i in ideas" value="i.ideaCode">{{ i.text }}<option>
</select>
setCodeAndLabelForForm: function() {
var me = this;
this.ideas.forEach(function(i) {
if(i.ideaCode == me.currentForm.ideaCode) {
me.currentForm.ideaCode = i.selectedIdea.ideaCode;
me.currentForm.text = i.text;
... do stuff & run callbacks ...
}
});
}
... but it just seems terrible. Any better suggestions?
You can implement like this:
Create empty object data to track the selected value:
currentForm: {}
Watch currentForm on the model and pass the selected object:
<select v-model="currentForm" #change="setCodeAndLabelForForm(currentForm)">
Pass in the selected value in option: (you were doing right in this step, but I just changed i to idea as it's little confusing looping index)
<option v-for="idea in ideas" :value="idea">{{ idea.text }}<option>
Apply your method:
setCodeAndLabelForForm(selected) {
// Now, you have the user selected object
}
A little bit better workaround: use index in v-for
<select v-model="selIdeaIndex" #change="setCodeAndLabelForForm">
<option v-for="(i,idx) in ideas" value="idx">{{ i.text }}<option>
</select>
For the js:
data: {
selIdeaIndex:null,
ideas: [
{ id: 1, code: "01A", text: "option 1", props: [] },
{ id: 2, code: "02A", text: "option 2 , props: [{ details: "something" }]}
]
},
methods:{
setCodeAndLabelForForm: function() {
var selIdea = this.ideas[this.selIdeaIndex];
//Do whatever you wanna do with this selIdea.
}
}
I don't know if this is the best solution, but I solve this problem using computed properties like this:
In the JavaScript file (ES6):
data () {
return {
options: [
{ id: 1, text: "option 1" },
{ id: 2, text: "option 2" }
],
selectedOptionId: 1
}
},
computed: {
selectedOption () {
return _.find(this.options, (option) => {
return option.id === this.selectedOptionId
});
}
}
In the HTML file:
<select v-model="selectedOptionId">
<option v-for="option in options" :value="option.id" :key="option.id">{{ option.text }}<option>
</select>
The '_' symbol is a common JavaScript library called Lodash and I highly recommend the usage. It can make you save some precious time.
If you know your options will only come from that v-for="i in ideas" then the <option> indexes will be the same as the item indexes.
Thus <select>.selectedIndex will be the index of the selected this.item.
new Vue({
el: '#app',
data: {
ideas: [
{ id: 1, code: "01A", text: "option 1", props: [] },
{ id: 2, code: "02A", text: "option 2" , props: [{ details: "something" }]}
],
currentForm: {ideaCode: "01A", text: "option 1"}
},
methods: {
setCodeAndLabelForForm: function(selectedIndex) {
var selectedIdea = this.ideas[selectedIndex];
this.currentForm = {ideaCode: selectedIdea.code, text: selectedIdea.text};
}
}
})
<script src="https://unpkg.com/vue#2.5.13/dist/vue.js"></script>
<div id="app">
<select v-model="currentForm.ideaCode" #change="setCodeAndLabelForForm($event.target.selectedIndex)">
<option v-for="i in ideas" :value="i.code">{{ i.text }}</option>
</select>
<br> currentForm: {{ currentForm }}
</div>
Differences from yours: #change="setCodeAndLabelForForm($event.target.selectedIndex)" and the setCodeAndLabelForForm implementation.
A humble way is using $ref.
There is a solution using $ref and #change.
Vue.js get selected options' raw object
It's been a while since I asked this question and there have been good suggestions for handling this situation. I cannot remember the exact business-case presented here, but just by a quick glance it looks like I couldn't figure out how to set/initialize right selection afterwards, because handling just the #change event is childs play -- it's the pairing of one single value against list of object-based-options which is harder. What I was most likely looking for was something AngularJS used to have (track-by -property, which matches any given value against selected-option).
Personally now-a-days I would separate UI-logics instead of trying to force 'em to blend together. Most viable approach for myself would be handling list of options and the selected option as one logical area (ie. data: { list: [...options], selectedIdea: Object }) and separate the "currentForm"-object from selection. Let's break this out:
selectedIdea is something which needs to trigger change into currentForm-object. It's not any kind of hybrid-model, it's just a plain object, one of the available selections, pure and simple.
... and once again: Whenever selectedValue === one of the options, the select-dropdown is automatically set to right selection.
"currentForm"-object has a property which can be used to set the selectedIdea. In this case it's the "ideaCode". This ideaCode doesn't automatically do any pairing or such Component logic needs to represent the rules, which trigger selecting the correct option, which matches "ideaCode".
Just an extra-though: selectedIdea and currentForm-object are two different logical elements. They could be even separated to different components if one would want do, and in some cases it's really good thing to separate 'em.
So by these statements I guess I would change my select's v-model to be exactly what it's supposed to be: One of the selected objects (change v-model="currentForm.ideaCode" into v-model="selectedIdea" or such). Then I would simply add watcher for that selectedIdea and make any alterations to currentForm-object from there.
How about initializing that option by currentForm.ideaCode ? Do one of the following on create-method:
Iterate list of available options. When you find option where currentForm.ideaCode == option.code => this.selectedIdea = option
... or use ecmascript find-method to do the same
... or use underscore/lodash find-method to do the same
Another way would be by using computed value, as suggested Augusto Escobar. Also $ref would work, as suggested by feng zhang, but this approach would still require solution for initializing correct option afterwards (when loading editor with initial values). Thanks to Bhojendra Rauniyar as well -- you were right all along, but I just couldn't comprehend the answer as I couldn't have figured out how to backtrack initial selection.
Thanks for all the suggestions over the year!
Here's the markup:
<ul>
<li v-for="(topic,label,index) in guides" :key="index">
<ul>
<strong> {{label}} </strong>
<li v-for="rule in topic">
{{rule.val}},
{{Object.keys(topic)[0]}}
</li>
</ul>
</li>
And here's the data for this list:
data: {
guides: {
"CSS" : {
1502983185472 : {
"modifiedby" : "bkokot",
"val" : "When adding new rule, use classes instead of ID whenever possible"
},
1502983192513 : {
"modifiedby" : "bkokot",
"val" : "Some other rule"
},
},
"Other" : {
1502628612513 : {
"modifiedby" : "dleon",
"val" : "Some test text"
},
1502982934236 : {
"modifiedby" : "bkokot",
"val" : "Another version of text"
},
}
}
}
So as you can see there is a "guides" property which is an object of other objects that do have inner objects too.
All I want is to get the keys from inner (second) loop (numbers "1502983185472" etc).
The only solution that i see right now is "Object.keys(topic)[0]", but is there a more accurate alternative in vuejs for this?
Adding key, index parameters to the second loop(with new unique variable names) doesn't work for me.
Here's a working fiddle: https://jsfiddle.net/thyla/yeuahvkc/1/
Please share your thoughts.
If there is no good solution for this - may it be a nice topic for a feature request in Vuejs repo(unless I'm missing something terrible here)?
Generally if you're curious - that number is a momentjs timestamp - I'm using this data in firebase, and saving initial timestamp as an object key seemed to be a pretty nice solution (since we need a key anyway, to save some space - I can use this key instead of another extra timestamp property in my object, this also makes the instance very 'targetable' in firebase).
Thank you in advance ! Cheers!
p.s: another possible solution is converting inner loop (css, other) from objects into arrays and using time-stamp as another object property, but I'm using firebase - saving this data as an object gives me an ability to quickly access some instance without parsing the entire parent object/array, makes it more easy to filter, search, reupdate, etc - thus converting object into array is not a good solution for instance with very large number of items.
Your fiddle renders the number key of the first entry of a topic for each of the rules in that topic. I'm assuming you want to actually show the number key for each corresponding rule.
That value is passed as the second parameter in the v-for:
<li v-for="(rule, ruleID) in topic">
{{ rule.val }},
{{ ruleID }}
</li>
Here's a working fiddle.
Here's the documentation on using v-for with an object.
This can be solved by as follows in the second loop like
<li v-for="(rule,index) in topic">
{{rule.val}},
{{index}}
</li>
Please refer this fiddle => https://jsfiddle.net/yeuahvkc/7/
Use explicit definitions
Other answers use ".val", but it's not clear where that originates.
Instead, just declare everything, like:
<li v-for="(value, key, index) in rule">
{{key}}: {{value}} - {{index}}
</li>