im try to display some data using b-table and the formatter method using axios with the spread method but this its not displaying correctly.
this is what i have https://codepen.io/damian-garrido/pen/MWgxqeZ
html template
<div id="app">
<b-table
:fields="fields"
:items="items">
</b-table>
</div>
js file
window.onload = () => {
new Vue({
el: '#app',
data() {
return {
fields: [
{
key: 'owner',
label: 'Poke Owner'
},
{
key: 'pokemonIds',
label: 'Poke Name',
formatter: 'getPokeName'
}
],
items: [
{
owner: 'Juan',
pokemonIds: [3,4]
},
{
owner: 'Diego',
pokemonIds: [7,9,14]
}
]
}
},
methods: {
getPokeName: function (pokeIds) {
let promises = []
for (let id of pokeIds) {
promises.push(axios.get(`https://pokeapi.co/api/v2/pokemon/${id}`))
}
axios.all(promises)
.then( axios.spread((...responses) => {
let names = ''
for (let r of responses) {
names += r.data.name + ', '
}
console.log(names)
return names
}))
}
}
})
}
the console.log return the names, as i need, but not display this on the table.
Table formatter methods must be synchronous.
You would need to make your formatter an async method that uses await to return the value from your formatter. Note that this will slow your app rendering to a crawl as each row will have to wait for the async call to finish before it can render the table row.
Your best bet would be to do the lookup processing in your app, and then pass that data to the table.
Related
I've gotten this menu to work without filtering it, but now I'm doing an ajax request to filter out menu items the user isn't supposed to see, and I'm having some trouble to figure out how to set the resulting menu data, the line that is not working is commented below:
<script>
import { ref } from 'vue';
import axios from 'axios';
var currentSelected = 'device_access';
var menuData = [
{
text: 'Device Access',
id: 'device_access',
children: [
{
text: 'Interactive',
link: '/connection_center'
},{
text: 'Reservation',
link: '/reserve_probe'
}, {
text: 'Reservation Vue',
link: '/reservation.html'
}
]
}, {
text: 'Automation',
id: 'automation',
show: ['is_mxadmin', 'can_schedule_scripts'],
children: [
{
text: 'Builder',
link: '/builder',
},{
text: 'Execution Results',
link: '/test_suite_execution_results'
},
]
}
];
function hasMatch(props, list) {
var match = false;
for (var i=0; i < list.length && !match; i++) {
match = props[list[i]];
}
return match;
}
export default {
name: 'Header',
setup() {
const cursorPosition = ref('0px');
const cursorWidth = ref('0px');
const cursorVisible = ref('visible');
//the menu is zero length until I get the data:
const menu = ref([]);
return {
menu,
cursorPosition,
cursorWidth,
cursorVisible
}
},
created() {
let that = this;
axios.get('navigation_props')
.then(function(res) {
var data = res.data;
var result = [];
menuData.forEach(function(item) {
if (!item.show || hasMatch(data, item.show)) {
var children = [];
item.children.forEach(function (child) {
if (!child.show || hasMatch(data, child.show)) {
children.push({ text: child.text, link: child.link });
}
});
if (children.length > 0) {
result.push({ text: item.text,
children: children, lengthClass: "length_" + children.length });
}
}
});
//continues after comment
this is probably the only thing wrong, I've run this in the debugger and I'm getting the
correct data:
that.$refs.menu = result;
since the menu is not being rebuilt, then this fails:
//this.restoreCursor();
})
.catch(error => {
console.log(error)
// Manage errors if found any
});
},
this.$refs is for template refs, which are not the same as the refs from setup().
And the data fetching in created() should probably be moved to onMounted() in setup(), where the axios.get() callback sets menu.value with the results:
import { onMounted, ref } from 'vue'
export default {
setup() {
const menu = ref([])
onMounted(() => {
axios.get(/*...*/).then(res => {
const results = /* massage res.data */
menu.value = results
})
})
return {
menu
}
}
}
I finally figured out the problem.
This code above will probably work with:
that.menu = result;
You don't need: that.$refs.menu
You can't do it in setup because for some reason "that" is not yet defined.
In my working code I added a new method:
methods: {
setMenuData: function() {
this.menu = filterMenu();
},
}
And "this" is properly defined inside them.
I've found a video from SharePointTech that explains how to change a textfield to a dropdown list on a List Form using data from open API. I'm trying to recreate it, but I'm hitting a roadblock with the new SharePoint Online. Instead of using "Country/Region", I created a new custom list with Company_Name. I took the person's code and made little changes that made a reference to "WorkCountry". When I save the changes (stop editing), the changes do not reflect and I get the same textfield. I had to use SharePoint Designer 2013 to create a new TestNewForm for new entry. Has anyone been able to reproduce this in SharePoint 2013 Designer? If so, would you be able an example?
I use jQuery's ajax method.
Updated code for your reference(you need to change the list name to your list name,InternalName is also):
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js">
</script>
<script>
var demo = window.demo || {};
demo.nodeTypes = {
commentNode: 8
};
demo.fetchCountries = function ($j) {
$.ajax({
url: _spPageContextInfo.siteAbsoluteUrl + "/_api/web/lists/getbytitle('Company_Name')/items",
type: "get",
headers: { "Accept": "application/json; odata=verbose" },
success: function (data) {
$j('table.ms-formtable td.ms-formbody').contents().filter(function () {
return (this.nodeType == demo.nodeTypes.commentNode);
}).each(function (idx, node) {
if (node.nodeValue.match(/FieldInternalName="Country_x002f_Region"/)) {
// Find existing text field (<input> tag)
var inputTag = $(this).parent().find('input');
// Create <select> tag out of retrieved countries
var optionMarkup = '<option value="">Choose one...</option>';
$j.each(data.d.results, function (idx, company) {
optionMarkup += '<option>' + company.Title + '</option>';
});
var selectTag = $j('<select>' + optionMarkup + '</select>');
// Initialize value of <select> tag from value of <input>
selectTag.val(inputTag.val());
// Wire up event handlers to keep <select> and <input> tags in sync
inputTag.on('change', function () {
selectTag.val(inputTag.val());
});
selectTag.on('change', function () {
inputTag.val(selectTag.val());
});
// Add <select> tag to form and hide <input> tag
inputTag.hide();
inputTag.after(selectTag);
}
});
},
error: function (data) {
console.log(data)
}
});
}
if (window.jQuery) {
jQuery(document).ready(function () {
(function ($j) {
demo.fetchCountries($j);
})(jQuery);
});
}
</script>
My source list:
Test result:
Updated:
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js">
</script>
<script>
var demo = window.demo || {};
demo.nodeTypes = {
commentNode: 8
};
demo.fetchCountries = function ($j) {
$.ajax({
url: 'https://restcountries.eu/rest/v1/all',
type: "get",
headers: { "Accept": "application/json; odata=verbose" },
success: function (data) {
$j('table.ms-formtable td.ms-formbody').contents().filter(function () {
return (this.nodeType == demo.nodeTypes.commentNode);
}).each(function (idx, node) {
if (node.nodeValue.match(/FieldInternalName="Country_x002f_Region"/)) {
// Find existing text field (<input> tag)
var inputTag = $(this).parent().find('input');
// Create <select> tag out of retrieved countries
var optionMarkup = '<option value="">Choose one...</option>';
$j.each(data, function (idx, company) {
optionMarkup += '<option>' + company.name + '</option>';
});
var selectTag = $j('<select>' + optionMarkup + '</select>');
// Initialize value of <select> tag from value of <input>
selectTag.val(inputTag.val());
// Wire up event handlers to keep <select> and <input> tags in sync
inputTag.on('change', function () {
selectTag.val(inputTag.val());
});
selectTag.on('change', function () {
inputTag.val(selectTag.val());
});
// Add <select> tag to form and hide <input> tag
inputTag.hide();
inputTag.after(selectTag);
}
});
},
error: function (data) {
console.log(data)
}
});
}
if (window.jQuery) {
jQuery(document).ready(function () {
(function ($j) {
demo.fetchCountries($j);
})(jQuery);
});
}
</script>
The difference in API will not have a great effect, the key is here '$ j.each (data, function (idx, company) {'. The structure of the return value of different APIs are different, you need to find useful data in return value.
I'm using react-select library to display a select box. I'm using Select.Async because I need to pull my options from an API. I use Select with loadOptions and it works during the intial page render. However, I'm also using redux-form which can change the value of a Field dynamically (using change). However, when I change the value of the Field like this, the value of the input does change (and I can verify this), but react-select's loadOptions is never called again (even though I thought it was supposed to be listening to a change of value). My question is, is there a way to dynamicaly call loadOptions every time the input value changes?
Thanks,
Edit: Answered on github here
this.state = {
store: '',
};
this.handleStoreSelect = this.handleStoreSelect.bind(this);
handleStoreSelect = (item) => {
this.setState({
store: item.value
}
};
<Select.Async
name="storeID"
value={this.state.store}
loadOptions={getStores}
onChange={this.handleStoreSelect}
/>
const getStores = () => {
return fetch(
"api to be hit",
{
method: 'get',
headers: {
'Content-Type': 'application/json'
}
}
)
.then(response => {
if(response.status >= 400){
throw new Error("error");
}
return response.json()
})
.then(stores => {
let ret = [];
for(let store of stores) {
ret.push({value: store._id, label: store.name})
}
return {options: ret};
})
.catch(err => {
console.log('could not fetch data');
console.log(err);
return {options: []}
})
};
Using this we can fetch the data and pass this object in the loadoptions.
copy this code outside the class. and also i'm posting the code to be implemented for loadoptions
It might be a better solution than this, but a quick one is to set a ref to your Select.Async component, and when a change action is triggered (like the change of an input - your case, or one button click event - like in the code below) you can update its options. I'm using a similar example with the example of their docs.
class YourClass extends React.Component {
getOptions = (input, callback) => {
setTimeout(function () {
callback(null, {
options: [
{value: 'one', label: 'One'},
{value: 'two', label: 'Two'}
]
});
}, 500);
};
updateOptions = () => {
this.selectAsync.state.options.push(
{value: 'three', label: 'Three'}
)
}
render() {
let props = this.props;
return (
<div>
<Select.Async
ref={selectAsync => this.selectAsync = selectAsync}
loadOptions={this.getOptions}
/>
<button onClick={this.updateOptions}>
Load more items
</button>
</div>
)
}
}
Trying to use jquery-chosen with vue, the problem is that this plugin hides the actual select that I applied v-model, so when I select a value vue doesn't recognize it as a select change event and model value is not updated.
The value of the select is being changed actually when I select something, I've inspected this with console.log to see the selected value.
http://jsfiddle.net/qfy6s9Lj/3/
I could do vm.$data.city = $('.cs-select').val(), that seems to work,
But is there another option? If the value of the select was changed why vue doesn't see this?
#swift's answer got pretty close, but as #bertrand pointed out, it doesn't work for multiselects. I've worked something out that works with both cases: http://jsfiddle.net/seanwash/sz8s99xx/
I would have just commented but I don't have enough rep to do so.
Vue.directive('chosen', {
twoWay: true, // note the two-way binding
bind: function () {
$(this.el)
.chosen({
inherit_select_classes: true,
width: '30%',
disable_search_threshold: 999
})
.change(function(ev) {
// two-way set
// Construct array of selected options
var i, len, option, ref;
var values = [];
ref = this.el.selectedOptions;
for (i = 0, len = ref.length; i < len; i++) {
option = ref[i];
values.push(option.value)
}
this.set(values);
}.bind(this));
},
update: function(nv, ov) {
// note that we have to notify chosen about update
$(this.el).trigger("chosen:updated");
}
});
var vm = new Vue({
data: {
city: 'Toronto',
cities: [{text: 'Toronto', value: 'Toronto'},
{text: 'Orleans', value: 'Orleans'}]
}
}).$mount("#search-results");
I am opened for other suggestions, but for the time-being I did it this way:
html
<div id='search-results'>
{{city}}
<select class="cs-select" v-model='city'>
<option value="Toronto">Toronto</option>
<option value="Orleans">Orleans</option>
</select>
</div>
js
window.vm = new Vue({
el: '#search-results',
data: {
city: 'Toronto',
}
})
$('.cs-select').chosen({
inherit_select_classes: true,
width: '30%'
}).change( function() {
vm.$data.city = $('.cs-select').val()
})
Update: Be advised this doesn't work from within a v-for loop. A related question that deals with that is available here.
Going off of #kaktuspalme's solution, and with help from my friend Joe Fleming, I came up with a solution that works with Vue 2 and allows single and multiple selections:
Vue.directive('chosen', {
inserted: function(el, binding, vnode) {
jQuery(el).chosen().change(function(event, change) {
if (Array.isArray(binding.value)) {
var selected = binding.value;
if (change.hasOwnProperty('selected')) {
selected.push(change.selected);
} else {
selected.splice(selected.indexOf(change.deselected), 1);
}
} else {
var keys = binding.expression.split('.');
var pointer = vnode.context;
while (keys.length > 1)
pointer = pointer[keys.shift()];
pointer[keys[0]] = change.selected;
}
});
},
componentUpdated: function(el, binding) {
jQuery(el).trigger("chosen:updated");
}
});
Use it like this:
<select v-model="mymodel" v-chosen="mymodel">...</select>
It works with multiple="multiple" and even with nested state, e.g.:
<select v-model="nested.mymodel" v-chosen="nested.mymodel">...</select>
See the fiddle here: https://jsfiddle.net/tylercollier/bvvvgyp0/5/
Answer:
http://jsfiddle.net/qfy6s9Lj/5/
<div id='search-results'>
Vue model value <br>
{{city}}
<hr>
Select value:
<select class="cs-select" v-chosen>
<option value="Toronto">Toronto</option>
<option value="Orleans">Orleans</option>
</select>
</div>
Vue.directive('chosen', {
bind: function () {
var vm = this.vm;
this.el.options = vm.cities;
this.el.value = vm.city;
$(this.el).chosen({
inherit_select_classes: true,
width: '30%',
disable_search_threshold: 999})
.change( function() {
vm.city = this.el.value;
}.bind(this)
);
}
});
var vm = new Vue({
data: {
city: 'Toronto',
cities: ['Toronto', 'Orleans']
}
}).$mount("#search-results");
UPDATE: an even better solution (thanks to simplesmiler):
http://jsfiddle.net/simplesmiler/qfy6s9Lj/8/
I made an update for vue2.
Vue.directive('chosen', {
selected: null,
inserted: function (el, binding) {
selected = binding.value;
$(el).chosen().change(function(event, change) {
if(change.hasOwnProperty('selected')) {
selected.push(change.selected);
} else {
selected.splice(selected.indexOf(change.deselected), 1);
}
});
},
componentUpdated: function(el, binding) {
selected = binding.value;
$(el).trigger("chosen:updated");
}
});
var vm = new Vue({
el: '#app',
data: {
selected: [],
cities: [{id: 1, value: "Toronto"}, {id: 2, value: "Orleans"}, {id: 3, value: "Bern"}]
}
});
See: https://jsfiddle.net/kaktuspalme/zenksm2b/
Code taken from #kaktuspalme answer. It works with non-multiple elements now and only for non-multiple.
Vue.directive('chosensingle', {
inserted: function (el, binding) {
var selected = binding.value;
$(el).chosen().change(function(event, change) {
if(change.hasOwnProperty('selected')) {
selected.value = change.selected;
} else {
selected.value ='';
}
});
},
componentUpdated: function(el, binding) {
$(el).trigger("chosen:updated");
}
});
Comments from #Tyler Collier are taken into account
But be carefully,property you use in v-model should be defined as array , e.g. applicantId: [] otherwise it doesn't work
I'm trying to create a list with endless scroll in angularjs. For this I need to fetch new data from an api and then append it to the existing results of a scope in angularjs. I have tried several methods, but none of them worked so far.
Currently this is my controller:
userControllers.controller('userListCtrl', ['$scope', 'User',
function($scope, User) {
$scope.users = User.query();
$scope.$watch('users');
$scope.orderProp = 'name';
window.addEventListener('scroll', function(event) {
if (document.body.offsetHeight < window.scrollY +
document.documentElement.clientHeight + 300) {
var promise = user.query();
$scope.users = $scope.users.concat(promise);
}
}, false);
}
]);
And this is my service:
userServices.factory('User', ['$resource',
function($resource) {
return $resource('api/users', {}, {
query: {
method: 'GET',
isArray: true
}
});
}
]);
How do I append new results to the scope instead of replacing the old ones?
I think you may need to use $scope.apply()
When the promise returns, because it isnt
Part of the angular execution loop.
Try something like:
User.query().then(function(){
$scope.apply(function(result){
// concat new users
});
});
The following code did the trick:
$scope.fetch = function() {
// Use User.query().$promise.then(...) to parse the results
User.query().$promise.then(function(result) {
for(var i in result) {
// There is more data in the result than just the users, so check types.
if(result[i] instanceof User) {
// Never concat and set the results, just append them.
$scope.users.push(result[i]);
}
}
});
};
window.addEventListener('scroll', function(event) {
if (document.body.offsetHeight < window.scrollY +
document.documentElement.clientHeight + 300) {
$scope.fetch();
}
}, false);