I want the view to always be updated with the result of the latest call to searchWiki(). I've used a module that resolves repeated calls to $.ajax to the value returned by the most recent call.
It still seems to go out of sync and show the result of previous calls however. I'm guessing this is because setState is async? How should I keep the two async operations in sync?
In addition, I realize I should put in a debounce somewhere, but I'm not sure what I should debounce. handleChange, searchWiki or latestAjax?
Here is a demo: http://codepen.io/prashcr/pen/obXvWv
Try typing stuff then backspacing to see what I mean.
Search component
<div style={style}>
<input
value={this.props.search}
// this calls searchWiki with e.target.value
onInput={this.handleChange}
/>
</div>
searchWiki function of parent component
searchWiki (search) {
console.log('searchWiki called with: ' + search);
if (!search) {
this.setState({results: []});
}
else {
// "latest" taken from https://github.com/bjoerge/promise-latest
let latestAjax = latest($.ajax);
let options = {
url: this.props.url + search,
dataType: 'jsonp'
};
latestAjax(options)
.then(data => {
var results = data.query.search.map(res => {
res.url = 'http://en.wikipedia.org/wiki/' + encodeURIComponent(res.title);
return res;
});
this.setState({results: results});
})
.error(e => console.log(e.message));
}
}
The handleChange function is too generic to be debounced with a hardcoded value as you might want to use this search component elsewhere. However, you still want to catch the repeating action as early as possible and ensure that it never does any unnecessary work.
Therefore I would suggest that you debounce the handleChange function with an optional prop, defaulting to 0ms.
getDefaultProps() {
return {
debounce: 0
};
},
render() {
// ...
return (
<div style={style}>
<input
// ...
onInput={debounce(this.handleChange, this.props.debounce)}/>
</div>
);
}
Then make sure you pass this prop whenever you want to debounce the handler.
<Search onSearch={this.searchWiki} debounce={1000} />
Your other problem is happening because you are calling latest inside your searchWiki function and you only call the returned function once! Each time you call searchWiki you create a new latestAjax function.
For it to work, you'll need to call the returned function multiple times.
This means defining the wrapped $.ajax function outside of searchWiki function.
latestAjax: latest($.ajax),
searchWiki(search) {
// ...
this.latestAjax(options)
.then(data => {
});
}
Related
I'm a frustrated Vue.js noobie coming from jQuery.
I'm trying to do something very basic for a simple project: Delete an article but only after ajax response. While waiting, for the response there's a spinner. (I don't want components or vue files. It's a simple, single file app)
I've been hacking away at it for a few days now and I can't grasp some basic concepts it seems. (Or I want it to behave like jquery)
Fiddle
window.app = new Vue({
el: '#app',
data: {
name: '',
posts: [],
loadingItems: [],
},
created() {
this.fetchData();
},
methods:{
fetchData() {
axios.get('https://jsonplaceholder.typicode.com/posts').then(response => {
this.posts = response.data.slice(0,20);
});
},
removeItem(item) {
var index = this.posts.indexOf(item);
//var loadingIndex = this.loadingItems.push(item) - 1;
//console.log(loadingIndex);
item.isLoading = true;
//Update vue array on the fly hack - https://vuejs.org/2016/02/06/common-gotchas/
this.posts.splice(index, 1,item);
axios.post('//jsfiddle.net/echo/json/', "json='{status:success}'&delay=2")
.then(response => {
this.posts.splice(index, 1);
//this.loadingItems.splice(loadingIndex, 1);
//this.loadingItems.pop(item);
//item.isLoading = false;
//this.posts.splice(index, 1,item);
});
}
},
computed: {
showAlert() {
return this.name.length > 4 ? true : false
}
}
})
<div id="app">
<div v-for="(post,index) in posts" :key="post.id">
<b-card class="mb-2">
<b-card-title>{{post.title}}</b-card-title>
<b-card-text>{{post.body}}</b-card-text>
<a href="#" #click.prevent="removeItem(post)" class="card-link">
Remove
<span v-show="post.isLoading" class="spinner"></span>
</a>
</b-card>
</div>
</div>
Works fine for deleting them 1 by 1 one but not when you click on multiple at the same time, since the index is different by the time the request comes back and it splices the wrong item.
What I've tried so far:
First, it took me a day to figure out that item.isLoading = true; won't work if it wasn't present when the data was first observed (or fetched). However, I don't want to add the property to the database just for a loading animation. So the workaround was to do this.posts.splice(index, 1,item); to "force" Vue to notice my change. Already feels hacky.
Also tried using an array LoadingItems and pushing them while waiting. Didn't work due to the same problem: don't know which one to remove based on index alone.
Studying the TODO app didn't help since it's not quite addressing handling async ajax responses or adding properties at runtime.
Is the best way to do it by using post.id and then trying to pass it and find it back in the array? Really confused and thinking jQuery would have been easier.
Any help will be appreciated. Thanks!
Works fine for deleting them 1 by 1 one but not when you click on multiple at the same time, since the index is different by the time the request comes back and it splices the wrong item.
Don't save the index in a variable. Calculate it every time you need it:
removeItem(item) {
item.isLoading = true;
this.posts.splice(this.posts.indexOf(item), 1, item);
axios.post('/echo/json/', "json='{status:success}'&delay=2")
.then(response => {
this.posts.splice(this.posts.indexOf(item), 1);
});
}
I have an issue with observables being fired after content is updated via ajax and javascript is re-initialized via .trigger('contentUpdated'). This all works when the scripts are rendered initially on page load but when they are added via ajax I cannot get the observables to update. For demo purposes I've simplified by logic but basically I have a block that gets loaded via ajax for a product collection like so:
myblock.phtml
<div class="wrapper-id-1">
<!-- this is what gets appended via ajax
<div id="product-item-<?php echo $productId;?>">
<span data-bind="text: someObservable()"></span>
...
</div>
<script type="text/x-magento-init">
{
"#product-item-<?php echo $productId;?>": {
"path/to/component":{
"some":"vars"
}
}
</script>
<!--and ajax append ends here -->
</div>
In the component that gets bound to the element I have:
component.js
...
this.someObservable: ko.observable('default value'),
initialize: function () {
var self = this;
this.anotherComponentModel().value.subscribe(function(data){
self.someObservable(data['value']);
},this);
},
...
the ajax that calls and loads the collection:
ajaxComponent.js
$.ajax({
url: 'route/to/controller?cat=' + categoryId,
success: function (data) {
$('.wrapper-id-' + categoryId).empty().append('<h2>' + categoryTitle + '</h2>' + data).show().trigger('contentUpdated');
}
})
...
I see that the component(component.js) gets initialized when contentUpdated is triggered and it has all of the correct data that is needed. However the observables to not fire and the data is not updated to the DOM. This an issue with scope? Or to I need to re-initialize the observables? I tried doing this via not binding directly to the component ie:
<script type="text/x-magento-init">
{
// components initialized without binding to an element
"*": {
"<js_component3>": ...
}
}
</script>
but it achieves the same result.
What am I missing here.
in cf9 i have a page where i want to check if field value exist in db (via ajax). If it doesn't exist, i want to stop processing (return false). Everything works fine, except i don't know how to pass back to the main function the result of the ajax call
please help
<cfajaximport tags="cfmessagebox, cfwindow, cfajaxproxy">
<cfajaxproxy cfc="reqfunc" jsclassname="jsobj" />
<script language="JavaScript">
function checkRequired() {
var testVal = document.getElementById('myField').value;
return testAjax(testVal);
/* more processing that should stop if ajaxCallBack returns false */
}
function testAjax(testVal) {
var instance = new jsobj();
instance.setCallbackHandler(ajaxCallBack);
instance.checkProfile(testVal);
}
function ajaxCallBack(returns) {
alert(returns);
// returns correctly "true" if value checks against db, "false" if it doesn't
// HOW DO I PASS THIS VALUE BACK TO checkRequired ???
}
</script>
<form>
<input type="text" name="myField" id="myField" value=""><p>
<input type="button" value="Check with Ajax" onClick="return checkRequired()">
</form>
many thanks
Unless you build your main function to 'wait' for the return, you can't return your result to that instance of the function; it has already exited, so to speak. Using cfajax it is probably possible to tweak the main function to call and wait, but the simple solution is to have the callback subsequently recall your main function and treat the existence of the result/return as the flag as to whether to process or call the ajax.
function checkRequired(return) {
if(return != null) {
/* more processing */
} else {
testAjax(testVal);
}
}
function ajaxCB(return) {
checkRequired(return);
}
I would probably refactor a bit more but you should get the idea.
it's really kind of a hack, not what i was looking for, but for what it's worth: if i put this stanza at the very end of my function, with the callBack collapsed within the main function, it would work
function checkRequired() {
var testVal = document.getElementById('myField').value;
var instance = new jsobj();
var r = instance.setCallbackHandler(
function(returns) {
if(returns == 1) {
document.getElementById('testForm').submit();
} else { alert("Something wrong"); }
}
);
instance.checkProfile(testVal);
}
This is my stack : Ember.js + Express/Node.js
Say i have an Endpoint as \posts, it will return an array of objects.
and i have following template named allPosts :
{{#each post in content}}
<p>{{post.body}} </p>
{{/each}}
Route:
App.AllPosts =Ember.Object.extend({
body : null
})
App.AllPostsRoute = Ember.Route.extend({
setupController : function(controller,model){
controller.set('content',model);
}
});
And controller as
App.AllPostsController = Ember.Controller.extend({
actions: {
save : fucntion(){
// Get And update data from server via ajax
}
}
});
I want to keep data in sync with data on server, for this i planned to use setInterval and call the save action above every 1000ms to update the data. But it doesn't work. i used setInterval like this
setInterval(App.AllPostsController.actions.save,3000);
I DONT want to use Ember Data. As the data is dependent on another Node app which runs server side.
You're trying to run an action on a type, not an instance of the controller. Instead you should start saving when you actually hit the route and controller, setupController is a good place to accomplish this.
App.AllPostsRoute = Ember.Route.extend({
setupController : function(controller,model){
controller.set('content',model); // in this code model would be blank, I'm assuming you're leaving out code
this.startSaving(controller);
},
willTransition: function(transition){
//if I'm leaving, this.stopSaving();
},
isSaving: false,
startSaving: function(controller){
this.set('isSaving', true);
this.realSave(controller);
},
realSave: function(controller){
if(!this.get('isSaving')) return;
Em.run.later(function(){
controller.send('save');
}
},
stopSaving: function(){
this.set('isSaving', false);
}
});
I have a form which contains a hidden field;
<input name="qsID" type="hidden" value="1368113958" />
This is a hidden field added by Concrete5.
When submitting the form its like the hidden field does not exist, Concrete5 (PHP) cannot see the hidden field so presumably CasperJS is not sending the hidden field.
Why is this happening?
Update
Using var_dump I can see that the whole $_POST array is empty
Update 2
It seems that this single piece of code is the difference between the form being posted correctly and failing;
casper.waitForSelector("#Question33",
function success() {
this.test.assertExists("#Question33");
this.click("#Question33");
},
function fail() {
this.test.assertExists("#Question33");
});
This code also breaks the posting of form data
casper.waitForSelector("form#miniSurveyView576 input[name='Question34']",
function success() {
this.test.assertExists("form#miniSurveyView576 input[name='Question34']");
this.click("form#miniSurveyView576 input[name='Question34']");
},
function fail() {
this.test.assertExists("form#miniSurveyView576 input[name='Question34']");
});
I basically had to scrap this code as it did not work at all.
What I ended up with was something like this
casper.test.begin('web site up', 4, function(test) {
casper.start(url).then(function() {
this.test.assert(
this.getCurrentUrl() === url, 'url is the one expected'
);
this.test.assertHttpStatus(200, url + ' is up');
functions.viewPortCapture(casper, viewports[0], "1001");
test.assertExists(x("//a[normalize-space(text())='ABC']"));
this.click(x("//a[normalize-space(text())='ABC']"));
this.waitForUrl(/abc\/$/, function(){
test.assertExists("input[type='submit']");
});
});
casper.run(function() {
test.done();
});
});