I am writing a search function at the moment for my Backbone application, the idea is that a user can enter a string and the app will search for and return any matching models where the string appears in any of its attributes. So far I have the following,
view function run on keyup,
this.results = this.collection.search(letters);
This runs the following code located in the collection,
search: function( filterValue ) {
filterValue = filterValue.toLowerCase();
var filterThroughValue = function(data) {
return _.some(_.values(data.toJSON()), function(value) {
console.log(value);
if(value != undefined) {
value = (!isNaN(value) ? value.toString() : value);
return value.toLowerCase().indexOf(filterValue) >= 0;
}
});
};
return App.Collections.filterCollection = this.filter(filterThroughValue);
}
However running this I get the following error,
Uncaught TypeError: undefined is not a function
this error is shown as being the line return value.toLowerCase().indexOf(filterValue) >= 0; and this error gets returned whether or not I use a string I know exists in a model of the collection.
Is there a fix for this, or a better way of searching and returning only models that have models that contain the search string?
Since you just want a string representation of value, you can probably just check if value has a toString method. Note that String also has a toString method, so this will work if value is a String.
return _.some(_.values(data.toJSON()), function(value) {
if(value && value.toString) {
return value.toString().toLowerCase().indexOf(filterValue) >= 0;
}
return false;
}
Related
I'm new to javascript, and scratching my head over this issue:
I used to use the following to grab a bunch of product titles from the page:
CODE 1:
var productTitles = document.querySelectorAll(".product-title");
Then I used the following code to ad a list of these titles to a form's textarea field:
CODE 2:
var MyTextAreaField = document.querySelector("#my-textarea-field");
MyTextAreaField.value = [...productTitles].map(el=>el.textContent).filter(txt=>txt !== 'None').join('\n');
The above worked great, however, I just changed CODE 1 to be a function instead (in order to conditionally return product titles)
The below code is just a rough example of what it looks like:
CODE 3:
var productTitleOne = document.querySelectorAll(".product-title1");
var productTitleTwo = document.querySelectorAll(".product-title2");
var productTitleThree = document.querySelectorAll(".product-title2");
function createProductTitles() {
if (productTypeOne == null) {
return productTitleOne.textContent;
} else if (productTypeTwo == "None") {
return productTitleTwo.textContent;
} else {
return productTitleThree.getAttribute("data-qty") + 'x' + selectionItem.textContent ;
}
}
Problem is, now code 2 no longer works, because it is no longer an Array
So, I tried doing this (adding my function to a variable):
var productTitles = createProductTitles();
But the following still doesn't work, because it's still not really an array
MyTextAreaField.value = [...productTitles].map(el=>el.textContent).filter(txt=>txt !== 'None').join('\n');
So how do I get the result of my function to post to my textarea field?
The problem is the value you're returning on createProductTitles in the Code 1 you're using the array returned by var productTitles = document.querySelectorAll(".product-title"); in the Code 3 you're returning the textContent of that selector, i.e. return productTitleOne.textContent;.
It's important to make a distinction between these two codes because they're sending different data types one is returning an array and the other is returning a string.
So, you need to change your function to return the same the result of the querySelectorAll function
var productTitleOne = document.querySelectorAll(".product-title1");
var productTitleTwo = document.querySelectorAll(".product-title2");
var productTitleThree = document.querySelectorAll(".product-title2");
function createProductTitles() {
if (productTypeOne == null) {
return productTitleOne;
} else if (productTypeTwo == "None") {
return productTitleTwo;
} else {
return productTitleThree
}
}
and then use your function
var productTitles = createProductTitles();
Your function in CODE 3 needs to change.
document.querySelectorAll() returns a NodeList (similar to an array). So it's no good to then try and access the textContent property or call getAttribute() as you do in that function, both of which should instead be called (if desired) on the individual Nodes in the NodeList.
You can modify that function so that the calls you have made take place on the individual Nodes using the spread operator and map function, similarly to how you did in CODE 2:
function createProductTitles() {
if (productTypeOne == null) {
return [ ...productTitleOne].map( productTitles => productTitles.textContent );
} else if (productTypeTwo == "None") {
return [ ...productTitleTwo].map( productTitles => productTitles.textContent );
} else {
return [...productTitleThree].map( productTitles => productTitles.getAttribute("data-qty") + 'x' + selectionItem.textContent );
}
}
This function will return an array of string values that you can do with as you wish, such as:
createProductTitles().filter(txt=>txt !== 'None').join('\n');
In my code I have to analyse JSON objects. I use a small function set:
visit = function(object) {
if (isIterable(object)) {
forEachIn(object, function (accessor, child) {
visit(child);
});
}
else {
var value = object;
console.log(value);
}
};
forEachIn = function(iterable, functionRef) {
for (var accessor in iterable) {
functionRef(accessor, iterable[accessor]);
}
};
isIterable = function(element) {
return isArray(element) || isObject(element);
};
isArray = function(element) {
return element.constructor == Array;
};
isObject = function(element) {
return element.constructor == Object;
};
If I throw now a JSON Object to the visit function, it give me just the value to the console. But I expected the key/value combination. Example:
Code throw
aa03ddbffe59448fb8a56f6b80e650053
But I expect
uuid: aa03ddbffe59448fb8a56f6b80e650053
Is there anything I misunderstand?
I think the value variable must contain a different type from what you're expecting. You could try putting a breakpoint on that line of code and inspecting the object to check what it is. You're expecting the value variable to contain an object with a single uuid property, but it looks to me like the variable actually just contains a string.
So i tried to modified the original jQuery's val() method using the following code
(function ($) {
var original_val = jQuery.fn.val;
jQuery.fn.val = function( value ) {
var elem = this[0], val = undefined;
// Set for value first, if its undefined
val = value ? value : "";
if (elem){
if (elem.hasAttribute('thisattribute')){
if (val){
if (typeof val === 'number'){
// Do something here if val is a typeof number
}
} else {
// Do something here if val doesn't exist
}
}
}
console.log("Let me see what is value ::: ", val);
console.log("Let me see what is elem ::: ", elem);
return original_val.apply(this, [val]);
}
})(jQuery);
Using the code above, i check if the input element has a certain attribute on it, then proceed with modifying the value, before passing it to the original jQuery's val() method.
Using this method i managed to modify the value when i use the following code
$(id).val(10000)
But when i tried to retrieve the value using bottom code, it failed
$(id).val()
Some more, i can no longer chain the val() method with other method like trim() after i modified it, as it throws me the following error
Uncaught TypeError: input.val(...).trim is not a function
What did i do wrong here?
It's because your code is always providing an argument to the original val when calling it, even when being used as a getter. So the original val always thinks it's being called to set the value.
I'd make the getter case an early exit from the function, right near the top:
if (!arguments.length) {
return original_val.call(this);
}
(That's the same check jQuery does.)
Some side notes:
This:
return original_val.apply(this, [val]);
can be more efficiently written like this:
return original_val.call(this, val);
No need to create that array.
In a couple of places, you're testing for falsiness but the code seems to be meant to check for undefined instead
Live Example, see *** comments:
(function($) {
var original_val = jQuery.fn.val;
jQuery.fn.val = function(value) {
// *** Early exit when used as a getter
if (!arguments.length) {
return original_val.call(this);
}
var elem = this[0],
val = undefined;
// Set for value first, if its undefined
// *** Note: This converts any falsy value to "", not just undefined.
// *** Later you have a check for `val` being a number. `0` is falsy.
val = value ? value : "";
if (elem) {
if (elem.hasAttribute('thisattribute')) {
if (val) { // *** Again, 0 is falsy
if (typeof val === 'number') {
// Do something here if val is a typeof number
}
} else {
// Do something here if val doesn't exist
}
// Just for the purposes of demonstration:
val = val.toUpperCase ? val.toUpperCase() : val;
}
}
console.log("Let me see what is value ::: ", val);
console.log("Let me see what is elem ::: ", elem);
return original_val.call(this, val);
}
})(jQuery);
// *** Setting a value
$("#txt").val("foo");
// *** Reading a value
console.log($("#txt").val());
<input type="text" id="txt" value="" thisattribute="">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
I have some search functionality that I am working on, every time a user types into a text input I filter a collection, here is the code,
userSearch: function() {
var that = this;
var letters = $('.js-user-search').val();
this.filteredCollection.reset(that.filterUsers( that.collection, letters));
var resultsList = new app.SearchUserResults({
collection: this.filteredCollection
});
resultsList.render();
},
filterUsers: function( collection, filterValue) {
var filteredCollection;
if (filterValue === "") {
return collection.toJSON();
}
return filteredCollection = collection.filter(function(data) {
return _.some(_.values(data.toJSON()), function(value) {
if( value != undefined ) {
value = (!isNaN(value) ? value.toString() : value);
//var re = /^(([^<>()[\]\\.,;:\s#\"]+(\.[^<>()[\]\\.,;:\s#\"]+)*)|(\".+\"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return value.indexOf(filterValue) >= 0;
}
});
});
}
As you can see from the code above, I pass a collection (users) and the search parameters to filterUsers(), that then returns a collection of matching models. I am then trying to render that into a list of search results ( links ), but the events on those links run several times (dependent on the length of the search string).
How can I build a list of results from the return collection? I have tried adding,
this.filteredCollection.on('reset', this.doSomething); however this never seems to get run, I have tried initialising my results view in the initialise function also, but I cannot pass the collection to that view as it is empty what is the best way to go?
you have to be careful with views in backbone. You keep adding a new searchresults view without removing the old one. Always keep a reference to views you add multiple times so that you can remove the previous one. I think this part will help you out:
var myCurrentSearchList = null;
userSearch: function() {
var that = this;
var letters = $('.js-user-search').val();
this.filteredCollection.reset(that.filterUsers( that.collection, letters));
if (myCurrentSearchList) {
myCurrentSearchList.remove();
}
var resultsList = new app.SearchUserResults({
collection: this.filteredCollection
});
myCurrentSearchList = resultsList;
resultsList.render();
},
http://backbonejs.org/#View-remove
Hi i need to iterate over each title in this code. However, i'm getting some error in console that states cannot use 'in' operator. This code works fine when i pass an id that is coming from database. But i need to pass in a string, then it throws error right after each function is called. I can't figure out why, one thing i can think of is there is possible JSON/string conflic. How do i resolve that. Any help would be appreciated thanks.
function getFilteredBySearch(searchString){
return priv.availablePrintables.filter(function(printableModel) {
var result = false;
var title = printableModel.getTitle();
$.each(title, function(idx, id) {
if (id == searchString) {
result = true;
return false; // break out of the 'each' loop
}
})
return result; // return from the callback
});
}
RESOLVED:
The following worked!
if ((printableModel.getTitle()).indexOf(searchString) > -1){
result = true;
console.log(result);
}
I think you're trying to iterate over the collection, when the filter method already does that for you. You don't need jQuery to loop over a number of items, when you only have one to examine. Try this:
function getFilteredBySearch(searchString){
return priv.availablePrintables.filter(function(printableModel) {
return (searchString == printableModel.getTitle());
});
}
(I'm not that familiar with backbone.js, so I might have the syntax slightly wrong)
This is only made based on assumptions: probably sometimes getTitle method return string and sometimes returns array of strings (because you've pointed out that console logs out ["book"] when you get title). You can check what type of object it returns:
function getFilteredBySearch(searchString){
return priv.availablePrintables.filter(function(printableModel) {
var title = printableModel.getTitle();
if(typeof title == 'string'){
//if it's string
return (title == searchString);
}else{
//if it's array
return (title.indexOf(searchString) != -1);
}
});
}