Global search box in angular - search

I want to implement a search box that changes what it searches based on whichever controller is being used. If you are on the "posts" view it will search the posts api, if you are on the videos view, it searches the videos api. It seems the search box would need its own controller maybe. I'm pretty sure I need to inject a search service into all the model controllers but I'm not exactly sure how to change the url it searches or tie the input to the different controller scopes.
So any ideas how to have a global search box that changes where it searches based on whichever controller is making use of it and tying its state back into a changing view?

To make a resource call dynamic api i would first create two $resources that map to your two endpoints, posts and videos. Then put an ng-change event on your global search that calls a function in your base controller.
This function firsts need to figure out what api to search. Then make the appropriate api call. The important part is in the callback and i think this is what you are looking for.
In the callback you could $broadcast the resp data from your api query. Each of your controllers will be listening for an event with an $on function. The listeners will then populate the correct scope variable with the callback data.
Pseudo below.
Same html layout with ng-change
<html>
<body ng-controller="AppController">
<form>
<label>Search</label>
<input ng-model="global.search" ng-change="apiSearch()" type="text" class="form-control" />
</form>
<div ui-view="posts">
<div ng-controller="PostController">
<p ng-repeat="post in posts | filter: global.search">{{ post.name }}</p>
</div>
</div>
<div ui-view="videos">
<div ng-controller="VideoController">
<p ng-repeat="video in videos | filter: global.search">{{ video.name }}</p>
</div>
</div>
</body>
</html>
AppController
.controller('AppController', function ($scope, PostService, VideoService) {
$scope.apiSearch = function() {
// Determine what service to use. Could look at the current url. Could set value on scope
// every time a controller is hit to know what your current controller is. If you need
// help with this part let me know.
var service = VideoService, eventName = 'video';
if ($rootScope.currentController == 'PostController') {
service = PostService;
eventName = 'post';
}
// Make call to service, service is either PostService or VideoService, based on your logic above.
// This is pseudo, i dont know what your call needs to look like.
service.query({query: $scope.global.search}, function(resp) {
// this is the callback you need to $broadcast the data to the child controllers
$scope.$broadcast(eventName, resp);
});
}
})
Each of your child controllers that display the results.
.controller('PostController', function($scope) {
// anytime an event is broadcasted with "post" as the key, $scope.posts will be populated with the
// callback response from your search api.
$scope.$on('post', function(event, data) {
$scope.posts = data;
});
})
.controller('VideoController', function($scope) {
$scope.$on('video', function(event, data) {
$scope.videos = data;
});
})

Client side filtering.
If you are not looking for anything to crazy that can be achieved in a super simple way for global search. I didnt even know if this would work so i just did a quick test and it does. Obviously this could be solved in a much more detailed and controlled way using services and injecting them where they are needed. But since i don't know excatly what you are looking for i will provide this solution, if you like it, great accept it. If you don't i could probably help you with service injection solution
Quick solution is to have an app wide contoller with $rootScope ng-model. Lets call it global.search.
$rootScope.global = {
search: ''
};
For the app wide search input.
<form>
<label>Search</label>
<input ng-model="global.search" type="text" class="form-control" />
</form>
In separate partials you just need to filter data based on the global.search ng-model. Two examples
<p ng-repeat="post in posts | filter: global.search">{{ post.name }}</p>
Second template with different scope
<p ng-repeat="video in videos | filter: global.search">{{ video.name }}</p>
Note how they both implement | filter: global.search. Whenever global.search changes, any filters in the current view will be changed. So posts will be filtered on the posts view, and videos on the videos view. While still using the same global.search ng-model.
I tested this, it does work. If you need more detail explaining the setup and child controller hierarchy let me know. Here is a quick look at a full template
<html>
<body ng-controller="AppController">
<form>
<label>Search</label>
<input ng-model="global.search" type="text" class="form-control" />
</form>
<div ui-view="posts">
<div ng-controller="PostController">
<p ng-repeat="post in posts | filter: global.search">{{ post.name }}</p>
</div>
</div>
<div ui-view="videos">
<div ng-controller="VideoController">
<p ng-repeat="video in videos | filter: global.search">{{ video.name }}</p>
</div>
</div>
</body>
</html>

Related

Checkbox value is refreshed after page reload in node JS

"I am creating TODO list using Node as backend. after adding every new item, a checkbox is also generating in front of them so I can apply "CSS line-through" to let user know that item is done or of no use. But when I add another item, the page refreshes and that checkbox is unchecked as I am not storing that value anywhere. Can you tell me how to store the value of that checkbox in the backend?
HTML -
<div class="box" >
<% for (var i=0; i<newListItems.length; i++) { %>
<div class="item">
<input type="checkbox" id="checkBox">
<p> <%= newListItems[i] %> </p>
</div>
<% } %>
<form action="/" method="post" class="item">
<input class="inputBox" type="text" name="newItem" placeholder="New item" autocomplete="off" required="required">
<button type="submit" name="list" value=<%= listTitle%>> +</button>
</form>
</div>
Node JS -
const items = [];
app.post("/", function(req, res){
let item = req.body.newItem;
items.push(item);
res.redirect("/");
});
The answer involves a lot of code, so I will give you a set of steps that can help in your case.
You need to change your data scheme. Currently looks like you are just storing the string in an array of items. You need to change it to be array of objects. Each object should have the field task and done. So you could know which task is done or not.
app.post("/", function(req, res) {
let item = req.body.newItem;
items.push({ name: item, done: false });
res.redirect("/");
});
Next step will be adding an endpoint that will be changing the done field of an array item to true.
Then on a front-end you will need to write some JS code that will be sending an HTTP request to the endpoint that marks the task as done. You need to use AJAX call for that, for example, NPM package axios.
Change the template to reflect the changes to the data. e.g. instead of <%= newListItems[i] %> do <%= newListItems[i].name %> and add logic to render checked checkbox based on done property.
It worth to mention, that you should not store data in memory, because once the process is done, you will lose your data. It is okay for learning purposes, but in production, you should use a database.

How can I render specific piece of data from my mongodb into my html?

It's my first project. I'm creating a website where administrator have the privilege to change home page texts. So I'm keeping all texts in a collection called "Texts" , each document has a text:value . When home page is rendered I use Texts.find() to return an array containing objects each has a "text" value. I link it to my home page using index.
Like this..
<h2>Texts[0].text</h2>, so if i have 100 texts I go all the way to <h2>Texts[100].text</h2>. and they are different texts and I need to put them in a specific order so I can't just throw them into my html.
I know that's so stupid , so I'm looking for some idea instead of this.
--------------Modification------------------>>>>
I tried using find method for arrays but it also is so tiring , so something simpler would be great , here's a portion of the code
<div class="card bg-dark text-white">
<img src="<%=imgsArr.find(x => x.name === 'main2').src%>" class="card-img" alt="...">
<div class="card-img-overlay ">
<h2 class="card-title"><%=textArr.find(x => x.id === 14).text%></h2>
<div class="triangle-up"></div>
<hr class="ml-0 ">
<div class="triangle-down"></div>
<p class="card-text "><%=textArr.find(x => x.id === 15).text%></p>
<div class="card-topic">
<h2 class="card-title" style=";"><%=textArr.find(x => x.id === 16).text%></h2>
<p class="card-text"><%=textArr.find(x => x.id === 17).text%></p>
</div>
<div class="card-topic">
<h2 class="card-title" style=""><%=textArr.find(x => x.id === 18).text%></h2>
<p class="card-text"><%=textArr.find(x => x.id === 19).text%></p>
</div>
<div class="btnOut ">
<button class="btn btn-lg shadow-lg ">MENU</button>
</div>
</div>
</div>
Good news is that for loops are always a good option. If you are using ejs or a similar rendering engine something like this should work, although you will have to tweak it a bit. Take a look are the EJS rendering engine documentation for an exact format, but it would look something like below and no matter how long the list gets it will render as much as it receives(if it is a lot you might want to consider using pagination)
<% for text in Texts.text %>
<% if text==="some value"%>
<h2>text</h2>
<% else %>
<p> text </p>
If your front-end is perhaps react.js or vue.js or similar the same prnciple would work but the format will be different.
No offense friend but you need to get some tutorials. But let me help you as best i can. Everything in a web application is somewhat defined. Meaning it falls into one predetermined category or another. When you get json for example you get something base on how that particular data is defined in the database. Hence you can get json data from your backend that looks
{ 'title':'sneaker', 'price':'200', 'quantity':'50' }
Now assuming it is a list of json objects what you can do is loop through this and assign them to tags based on their object key(because it gets converted to a javscript object). so again you code would look something like
<% for text in Texts.text %>
<h2>text.title</h2>
<div>
<p>text.price</p>
<p>text.quantity</p>
</div>
This would be how you would render data from your database(the format might not exactly be spot on). Dealing with forms is a whole other ball game. So get a book or some tutorial videos that discuss it. You will better understand how to handle it.
For your code you are being very rigid, at first glance there is already a pattern, work with that pattern, you code just needs a simple for loop from what i gather.
for(let i=0; let i>100; i++){
if (i%2===0){
console.log(<h2> i </h2>)
}else{
console.log(<p> i </p>
}
}
The reason i have resulted to pure javascript is so you can see what the inner workings of your render should look like. You can get the total number of ids in your database(that is what the integer 100 represents in this case), loop through one by one and produce what you want. It is still javascript, do not let the html throw you off

Why does Videogular put the video source on controller.config instead of on the $scope?

I have a basic Videogular video player setup to play videos from Firebase Storage. In the HTML view this works:
<div ng-controller="MyController as controller" class="videogular-container">
<videogular vg-theme="controller.config.theme.url">
<vg-media vg-src="controller.config.sources" vg-native-controls="true"></vg-media>
</videogular>
</div>
In the controller this works:
var ref = firebase.database().ref(); // Create Firebase reference
var obj = $firebaseObject(ref.child($routeParams.id)); // get the record with the key passed in from the URL
var controller = this; // controller refers to the controller object
obj.$loaded( // wait until the async data loads from the remote Firebase
function(data) {
// video player
controller.config = { // provides an object to the controller
preload: "auto",
sources: [
// My Firebase video
{src: $sce.trustAsResourceUrl($scope.wordObject.videos[0].videoURL), type: "video/" + $scope.wordObject.videos[0].videoMediaFormat},
// The Videogular test videos
{src: $sce.trustAsResourceUrl("http://static.videogular.com/assets/videos/videogular.mp4"), type: "video/mp4"},
{src: $sce.trustAsResourceUrl("http://static.videogular.com/assets/videos/videogular.webm"), type: "video/webm"},
{src: $sce.trustAsResourceUrl("http://static.videogular.com/assets/videos/videogular.ogg"), type: "video/ogg"}
],
theme: {
url: "http://www.videogular.com/styles/themes/default/latest/videogular.css"
}
};
},
function(error) {
console.log("Error: ", error)
});
Everything works, to play one video. Now I want to dynamically access arrays of videos by theme. E.g., the user clicks to see all my cat videos or clicks another button to see all my dog videos. I have the Firebase Storage URLs on the $scope and ng-repeat prints out the URLs in the view:
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12 text-center">
<h3>{{currentTheme}}</h3>
<div>
<div ng-repeat="video in currentVideos">
{{video.videoURL}}
</div>
</div>
</div>
</div>
That works great too. So to spin out a series of video players with all my cat videos I just have to make an ng-repeat with a new video player for each video, with the vg-src coming from the $scope:
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12 text-center">
<h3>{{currentTheme}}</h3>
<div>
<div ng-repeat="video in currentVideos">
<div ng-controller="MyController as controller" class="videogular-container">
<videogular vg-theme="controller.config.theme.url">
<vg-media vg-src="{{video.videoURL}}" vg-native-controls="true"></vg-media>
</videogular>
</div>
</div>
</div>
</div>
</div>
That doesn't work. The error is Error: [$parse:syntax], meaning there's an Angular syntax error. The syntax error goes away when I change the vg-src back to vg-src="controller.config.sources":
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12 text-center">
<h3>{{currentWord}}</h3>
<div>
<div ng-repeat="video in currentVideos">
<div ng-controller="EnglishController as controller" class="videogular-container">
<videogular vg-theme="controller.config.theme.url">
<vg-media vg-src="controller.config.sources" vg-native-controls="true"></vg-media>
</videogular>
</div>
</div>
</div>
</div>
</div>
That works. The problem is that vg-src="controller.config.sources" works but vg-src="{{video.videoURL}}" doesn't work. Why can't Videogular source videos from the $scope?
I tried to put my video sources from the $scope onto controller.config in the controller but this never worked. Should I try to do this again tomorrow? (It's late and I'm getting confused trying to figure out why I can't put my video sources from the $scope onto controller.config in the controller.)
I wrote the question before I went to bed and woke up with (what I hope is) the answer. {{video.videoURL}} inserts the URLs of the videos. controller.config.sources inserts an object with a lot of stuff. I'll try making an array of configured objects and see what happens!
...
Yep, that worked! I wrote a tutorial for a Videogular minimum install, using the $scope instead of controller.config. I don't understand why the official How To Start tutorial uses controller.config instead of the $scope.
...
I can get the one video to play from my array of cat videos when the user clicks "Cat Videos" but I can't get ng-repeat to spin out all the videos in the array.
In the controller when the user clicks the "Cat Videos" button the handler accesses the array of cat videos on Firebase Storage, iterates through the array with forEach, for each video in the array it creates a variable for the videoSource and another variable for the video file format (videoSourceType), then makes a videoObject with an array of sources and a theme, then pushes the videoObject into the array $scope.videoObjects.
$scope.videoObjects = [];
$scope.showVideosOfTheme = function() {
theme.videos.forEach(function(video) { // iterate through the array of videos
var i = 0;
var videoSource = $scope.currentVideos[i].videoURL; // set the video source
var videoSourceType = $scope.currentVideos[i].videoMediaFormat; // set the video format
var videoObject = { // make a video object
preload: "auto",
sources: [
{src: $sce.trustAsResourceUrl(videoSource), type: "video/" + videoSourceType},
],
theme: {
url: "http://www.videogular.com/styles/themes/default/latest/videogular.css"
}
};
$scope.videoObjects.push(videoObject);
i++;
});
};
In the HTML view ng-repeat iterates through the array $scope.videoObjects and fdor each video object spins out a new Videogular video player using the theme and the sources. This doesn't work and the error message is Error: [$parse:syntax], in other words, an Angular syntax error.
<div ng-repeat="video in videoObjects" class="videogular-container">
<videogular vg-theme="{{video.theme.url}}">
<vg-media vg-src="{{video.sources}}" vg-native-controls="true"></vg-media>
</videogular>
</div>
I'll keep working on it!

When should we use Vue.js's component

When I study the feature of Vue.js's component system. I feel confused when and where should we use this? In Vue.js's doc they said
Vue.js allows you to treat extended Vue subclasses as reusable
components that are conceptually similar to Web Components, without
requiring any polyfills.
But based on their example it doesn't clear to me how does it help to reuse. I even think it complex the logic flow.
For example, you use "alerts" a lot in your app. If you have experienced bootstrap, the alert would be like:
<div class="alert alert-danger">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<strong>Title!</strong> Alert body ...
</div>
Instead of writing it over and over again, you can actually make it into a component in Vue:
Vue.component('alert', {
props: ['type','bold','msg'],
data : function() { return { isShown: true }; },
methods : {
closeAlert : function() {
this.isShown = false;
}
}
});
And the HTML template (just to make it clear, I separate this from the Vue Comp above):
<div class="alert alert-{{ type }}" v-show="isShown">
<button type="button" class="close" v-on="click: closeAlert()">×</button>
<strong>{{ bold }}</strong> {{ msg }}
</div>
Then you can just call it like this:
<alert type="success|danger|warning|success" bold="Oops!" msg="This is the message"></alert>
Note that this is just a 4-lines of template code, imagine when your app uses lot of "widgets" with 100++ lines of code
Hope this answers..

meteor-typeahead: Listing and selecting

I have installed meteor-typeahead via npm. https://www.npmjs.org/package/meteor-typeahead
I have also installed
meteor add sergeyt:typeahead
from https://atmospherejs.com/sergeyt/typeahead
I am trying to get the data-source attribute example to function so I can display a list of countries when the user begins to type. I have inserted all countries into the collection :-
Country = new Meteor.Collection('country');
The collection is published and subscribed.
When I type into the input field, no suggestions appear. Is it something to do with activating the API? if so how do I do this? Please reference the website https://www.npmjs.org/package/meteor-typeahead
My form looks like this:
<template name="createpost">
<form class="form-horizontal" role="form" id="createpost">
<input class="form-control typeahead" name="country" type="text" placeholder="Country" autocomplete="off" spellcheck="off" data-source="country"/>
<input type="submit" value="post">
</form>
</template>
client.js
Template.createpost.helpers({
country: function(){
return Country.find().fetch().map(function(it){ return it.name; });
} });
In order to make your input to have typeahead completion you need:
Activate typeahead jQuery plugin using package API
Meteor.typeahead call in template rendered event handler.
Meteor.typeahead.inject call to activate typeahead plugin for elementes matched by CSS selector available on the page (see demo app).
Write 'data-source' function in your template understandable by typeahead plugin. It seems your 'data-source' function is correct.
Add CSS styles for typeahead input(s)/dropdown to your application. See example here in demo app.
Try this way in your template:
<input type="text" name="country" data-source="country"
data-template="country" data-value-key="name" data-select="selected">
Create template like country.html (for example /client/templates/country.html) which contains:
<template name="country">
<p>{{name}}</p>
</template>
In your client javascript:
Template.createpost.rendered = function() {
Meteor.typeahead.inject();
}
and
Template.createpost.helpers({
country: function() {
return Country.find().fetch().map(function(it){
return {name: it.name};
});
},
selected: function(event, suggestion, datasetName) {
console.log(suggestion); //or anything what you want after selection
}
})

Resources