I am writing a gulp plugin which sorts files in stream in order following a comment annotation in each file. I successfully sorted it but I don't know (because maybe I don't understand streams so deeply) how to return ordered stream to gulp pipeline.
Now, I have this and it works.
var through = require('through2');
module.exports = function (options) {
var graph = [],
files = {};
var orderedStream = through.obj();
var sortingStream = through.obj(function (file, enc, cb) {
//file.isNull() and file.isStream() code [...]
//get dependencies [...]
//graph.push(file with dependencies) [...]
files[file.path] = file;
cb();
}, function (cb) {
var ordered = toposort(graph).reverse();
async.eachSeries(ordered, function (filePath, callback) {
orderedStream.write(files[filePath], callback);
}, function () {
cb();
});
});
return {
findOrder : function () {
return sortingStream;
},
sortFiles : function () {
return orderedStream;
}
}
};
Usage of this approach is not as straightforward as I want. You have to use it like this (it works):
gulp.task('default', function () {
var scripts = myPlugin();
return gulp.src('src/**/*js')
.pipe(scripts.findOrder())
.pipe(scripts.sortFiles())
.pipe(concat('build.js'))
.pipe(gulp.dest('dist'));
});
I hope there is a solution which allows to use my plugin as follows:
gulp.task('default', function () {
return gulp.src('src/**/*js')
.pipe(myPlugin())
.pipe(concat('build.js'))
.pipe(gulp.dest('dist'));
});
I experimented a lot but I couldn't come up with any working solution. Any help is appreciated. Thank you.
When you have to first check all the stream then do something with it before returning it, use the flush function in order to provide the stream.
Here is an example:
var through = require('through2');
var files = [];
through.obj(function (chunk, enc, cb) {
//store chunk in the files variable, do not push them onto the stream with this.push
files.push(chunk);
cb();
}, function (cb) {
//here is the flush function, files is now correctly provided
files.sort(); // do what you want with the array
//now you can push whatever you want into the stream
this.push(files[0]);
cb();
});
Related
I am looking to assign as an object a Fetch API promise from a local GeoJSON file.
Here is the code
fetch("data/sites.geojson")
.then(function(response) {
return response.json();
})
.then(function(data) {
L.geoJSON(data, {
pointToLayer: styles_sites
}).addTo(map);
});
};
I tried the call back method, as advised here
Saving fetched JSON into variable
(EDIT) New code, but there is still a missing formal parameter
function getData("data/sites.geojson", cb) {
fetch("data/sites.geojson")
.then(function(response) {
return response.json();
})
.then(function(data) {
L.geoJSON(data, {
pointToLayer: styles_sites,
onEachFeature: function (feature, layer) {
layer.on('mouseover', function() {
layer.openPopup(layer.bindPopup("<b>"+feature.properties.nombre+"</b>"))
});
layer.on('mouseout', function() {
layer.closePopup();
});
layer.on('click', function () {
layer.bindPopup("<b>Nombre: </b>"+feature.properties.nombre+"<br><b>Barrio: </b>"+feature.properties.barrio+"<br><b>Tipo: </b>"+feature.properties.tipo+"<br><b>Ubicacion: </b>"+feature.properties.ubicacion+"<br><b>Correo: </b>"+feature.properties.contacto);
});
}
}).addTo(map);
.then(function(result) {
cb(result);
});
});
};
getData("data/sites.geojson", function (data) {
return console.log({data});
});
Most probably just incorrect syntax of your callback function:
// Use either arrow function
getData("data/sites.geojson", (data) => {
return console.log({data});
});
// or standard function
getData("data/sites.geojson", function (data) {
return console.log({data});
});
I found the way to work this out by adding within the fetch function, what I originally wanted to do on the map.
This was to add a L.controlLayer using the geojson as overlay.
This is the code that made it work:
let sites = getData()
.then((function(data) {
L.geoJSON(data, {
pointToLayer: styles_sites,
onEachFeature: function LayerControl(feature, layer) {
var popupText = "<b>" + feature.properties.nombre + "<br>";
layer.bindPopup(popupText);
category = feature.properties.tipo;
// Initialize the category array if not already set.
if (typeof categories[category] === "undefined") {
categories[category] = L.layerGroup().addTo(map);
layersControl.addOverlay(categories[category], category);
}
categories[category].addLayer(layer);
layer.on('mouseover', function() {
layer.openPopup(layer.bindPopup("<b>"+feature.properties.nombre+"</b>"))
});
layer.on('mouseout', function() {
layer.closePopup();
});
layer.on('click', function () {
layer.bindPopup("<b>Nombre: </b>"+feature.properties.nombre+"<br><b>Barrio: </b>"+feature.properties.barrio+"<br><b>Tipo: </b>"+feature.properties.tipo+"<br><b>Ubicacion: </b>"+feature.properties.ubicacion+"<br><b>Correo: </b>"+feature.properties.contacto);
});
}
}).addTo(map);
}));
Actually it comes from one of your answer on another post ghybs.
I´m having a hard time understanding how to perform this action(as the title says), and maybe someone could help me understand the process, my code is below:
My home-view-model:
var Observable = require("data/observable").Observable;
var ObservableArray = require("data/observable-array").ObservableArray;
var http = require("http");
function createViewModel() {
http.getJSON("http://myJsonfile").then(function (r) {
var arrNoticias = new ObservableArray(r.data);
return arrNoticias;
}, function (e) {
});
}
exports.createViewModel = createViewModel;
I have done a console.log of the arrNoticias before i have putted it inside a callback function and it returns [object object] etc...and then i have done this:
console.log(arrNoticias.getItem(0).titulo);
and it returns the info i need!.
Then in my home.js file i have this:
var observableModule = require("data/observable")
var ObservableArray = require("data/observable-array").ObservableArray;
var arrNoticias = require('./home-view-model.js');
console.log(arrNoticias.getItem(0).titulo);
and the result in the console is:
TypeError: arrNoticias.getItem is not a function. (In 'arrNoticias.getItem(0)', 'arrNoticias.getItem' is undefined)
My question is, how does this action is perform? passing the data from view-model to the .js file?
Thanks for your time
Regards
As that function send a URL request so probably it's an async function, which is on hold while requesting so that's why you get undefined. Normally, you will want your function that sends a URL request to return a promise. Based on that promise, you will the result as expected after the request is done. So:
function createViewModel() {
return new Promise<>((resolve, reject) => {
http.getJSON("http://myJsonfile").then(function (r) {
var arrNoticias = new ObservableArray(r.data);
resolve(arrNoticias);
}, function(e) {
reject(e);
});
}), (e) => {
console.log(e);
})
}
In home.js:
var homeVM= require('./home-view-model.js');
var arrNoticias;
homeVM.createViewModel().then(function(r) {
arrNoticias = r;
});
I'm using Bootstrap Typeahead to suggest som search results. The results are returned from a ajax ressource, and since this resource creates a delay, I'm experiencing a unfortunate effect.
Example:
If typing a 4 letter word, the suggestions will appear after 2 letters, I can then go through the results with the keys up/down, but suddenly the suggestions will reload because the last request has finished.
Is there any way to "cancel" any remaining, if user is currently using the keys up/down to go through the suggestions?
('#query').typeahead({
items: 4,
source: function (query,process) {
map = {};
$.getJSON('/app_dev.php/ajax/autosuggest/'+query, function (data) {
vehicles = [];
$.each(data, function(i,vehicle){
map[vehicle.full] = vehicle;
vehicles.push(vehicle.full);
});
process(vehicles);
});
},
updater: function (item) {
// do something here when item is selected
},
highlighter: function (item) {
return item;
},
matcher: function (item) {
return true;
}
});
I think the following will satisfy your needs (its hard to reproduce exactly) :
There is no easy way to abort a delayed response, but you could extend typeahead as I figured out here (without modifying bootstrap.js)
The concept is to catch keydown, detect if the event is KEY_UP or KEY_DOWN, set a flag is_browsing, and then abort process if is_browsing is true (that is, if the user has hitted KEY_UP or KEY_DOWN and no other keys afterwards).
Extending typeahead :
// save the original function object
var _superTypeahead = $.fn.typeahead;
// add is_browsing as a new flag
$.extend( _superTypeahead.defaults, {
is_browsing: false
});
// create a new constructor
var Typeahead = function(element, options) {
_superTypeahead.Constructor.apply( this, arguments )
}
// extend prototype and add a _super function
Typeahead.prototype = $.extend({}, _superTypeahead.Constructor.prototype, {
constructor: Typeahead
, _super: function() {
var args = $.makeArray(arguments)
// call bootstrap core
_superTypeahead.Constructor.prototype[args.shift()].apply(this, args)
}
//override typeahead original keydown
, keydown: function (e) {
this._super('keydown', e)
this.options.is_browsing = ($.inArray(e.keyCode, [40,38])>-1)
}
//override process, abort if user is browsing
, process: function (items) {
if (this.options.is_browsing) return
this._super('process', items)
}
});
// override the old initialization with the new constructor
$.fn.typeahead = $.extend(function(option) {
var args = $.makeArray(arguments),
option = args.shift()
// this is executed everytime element.modal() is called
return this.each(function() {
var $this = $(this)
var data = $this.data('typeahead'),
options = $.extend({}, _superTypeahead.defaults, $this.data(), typeof option == 'object' && option)
if (!data) {
$this.data('typeahead', (data = new Typeahead(this, options)))
}
if (typeof option == 'string') {
data[option].apply( data, args )
}
});
}, $.fn.typeahead);
This typeahead-extension could be placed anywhere, eg in a <script type="text/javascript"> -section
Testing the extension :
<input type="text" id="test" name="test" placeholder="type some text" data-provide="typeahead">
<script type="text/javascript">
$(document).ready(function() {
var url='typeahead.php';
$("#test").typeahead({
items : 10,
source: function (query, process) {
return $.get(url, { query: query }, function (data) {
return process(data.options);
});
}
});
});
</script>
A "serverside" PHP script that returns a lot of randomized options with forced delay, typeahead.php :
<?
header('Content-type: application/json');
$JSON='';
sleep(3); //delay execution in 3 secs
for ($count=0;$count<30000;$count++) {
if ($JSON!='') $JSON.=',';
//create random strings
$s=str_shuffle("abcdefghijklmnopq");
$JSON.='"'.$s.'"';
}
$JSON='{ "options": ['.$JSON.'] }';
echo $JSON;
?>
It really seems to work for me. But I cannot be sure that it will work in your case. Let me now if you have success or not.
How do I do this asynchronously?
var getData, myFunc;
getData = function() {
var data = "";
$.get("http://somewhere.com/data.xml", function(d) {
data = $("#selector", d).html();
});
return data; // does not work, because async callback not yet fired
};
myFunc = function() {
var data = getData();
// do something with data here
};
I am happy to completely re-factor to achieve what I want. I am just don't know what design pattern achieves this.
Well, you can't. You can return a promise though:
var getData, myFunc;
getData = function () {
var d = $.Deferred();
$.get("http://somewhere.com/data.xml", function (data) {
d.resolve($("#selector", data).html())
});
return d.promise();
};
getData().then(function (data) {
alert(data);
});
demo http://jsfiddle.net/W75Kt/2/
I am writing code below that parses a sites API one at a time, than tells an event queue it is ready for the next object to parse. I am having issues since I am still new to javascript scoping, and would like to emit from SiteParser or call the emitForNext function. I cannot seem to bring emitForNext into scope in the error callback.
function SiteParser(){
this.emitForNext = function (message) {
this.emit("next", message);
};
this.pullJSON = function (path, processJSON) { //processJSON is a callback function
var options = {
host: 'www.site.com',
port: 80,
path: path
}
//console.log("... processing "+path);
//pulls the entire json request via chunks
http.get(options, function (res) {
var resJSON = ''; //stores the comment JSON stream given in the res
res.on('data', function (chunk) {
resJSON+=chunk;
});
res.on('end', function () {
var obJSON = (JSON.parse(resJSON));
if (obJSON.hasOwnProperty("error")){
console.log(obJSON);
console.log('... ', path, ' does not exist');
//
//NEED A NEXT EVENT EMMITER HERE NEED TO FIGURE OUT SCOPE
//
//
} else {
processJSON(obJSON); //call the callback function
}
}) ;
}).on('error', function (e) {
emitForNext("got error: " + e.message);
});
};
}
JavaScript has function scoping, if you declare a variable with the var keyword, it will be local to the current function. When you access a variable, it will look to the scope chain which consist of the current function, it's parent function, …. Try:
function one() {
var foo = 'foo';
function two() {
console.log(foo) // undefined. I'll explain this
var foo = 'bar';
console.log(foo) // bar
}
two()
console.log(foo) // foo
}
one()
Most of the time we define variables at the beginning of functions, because a variable defined in function's body get hoisted. Basically, it means that it's available in the whole function, even before it's defined but in this case it's value is undefined.
For example if a variable is not defined we normally get a ReferenceError, but in the snippet below, both console.log() just print undefined.
function foo() {
console.log(bar);
if (0) {
var bar = 'bar';
}
console.log(bar);
}
So, a common practice is that, when you write long functions, you map this to self.
function SiteParser() {
var self = this;
// ...
.error('error', function(err) {
self.emitForNext("got " + err.message);
})
}
You should not write all your methods in the constructor, it's only usefull sometimes when we want privacy, but in this case you'd better use prototypes.
Putting this together, I would write:
var SiteParser = function() {};
SiteParser.prototype.emitForNext = function(message) {
this.emit("next", message);
};
SiteParser.prototype.pullJSON = function(path, processJSON) {
var self = this,
options = {
host: 'www.site.com',
port: 80,
path: path
};
http.get(options, function(res) {
// ...
}).on('error', function (e) {
self.emitForNext("got error: " + e.message);
});
};
To be able to access emitForNext, you need to call self.emitForNext, where self points to your instance of SiteParser.
Like so:
function SiteParser(){
this.emitForNext = function (message) {
this.emit("next", message);
};
this.pullJSON = function (path, processJSON) { //processJSON is a callback function
var options = {
host: 'www.site.com',
port: 80,
path: path
};
var self = this;
//console.log("... processing "+path);
//pulls the entire json request via chunks
http.get(options, function (res) {
var resJSON = ''; //stores the comment JSON stream given in the res
res.on('data', function (chunk) {
resJSON+=chunk;
});
res.on('end', function () {
var obJSON = (JSON.parse(resJSON));
if (obJSON.hasOwnProperty("error")){
console.log(obJSON);
console.log('... ', path, ' does not exist');
self.emitForNext(path + ' does not exist');
} else {
self.emitForNext('Successfully parsed the response');
processJSON(obJSON); //call the callback function
}
}) ;
}).on('error', function (e) {
self.emitForNext("got error: " + e.message);
});
};
}
However, it looks like you'd rather manage what you'll do next (like parsing the next object) in you callback, ie. in the body of processJSON.
You need to store link to 'this'' object in SiteParser local scope.
function SiteParser () {
var that = this; // store link to 'this' in local scope
this.emitForNext = function (message) {
this.emit("next", message);
};
this.pullJSON = function (path, processJSON) { //processJSON is a callback function
var options = {
host: 'www.site.com',
port: 80,
path: path
}
//console.log("... processing "+path);
//pulls the entire json request via chunks
http.get(options, function (res) {
var resJSON = ''; //stores the comment JSON stream given in the res
res.on('data', function (chunk) {
resJSON+=chunk;
});
res.on('end', function () {
var obJSON = (JSON.parse(resJSON));
if (obJSON.hasOwnProperty("error")){
console.log(obJSON);
console.log('... ', path, ' does not exist');
that.emitForNext();
} else {
processJSON(obJSON); //call the callback function
}
}) ;
}).on('error', function (e) {
that.emitForNext("got error: " + e.message);
});
};
}