I'm trying to figure out the best way to take a json object which I'm storing as a scope, and filter/query it to display specific data from within it.
For example:
$scope.myBook = {"bookName": "Whatever",
"pages": [
{"pageid": 1, "pageBody": "html content for the page", "pageTitle": "Page 1"},
{"pageid": 2, "pageBody": "html content for the page", "pageTitle": "Page 2"},
{"pageid": 3, "pageBody": "html content for the page", "pageTitle": "Page 3"},
]
}
How would I go about grabbing the object for pageid:2 ?
You can use this approach:
template:
<div ng-repeat="page in myBook.pages | filter:pageMatch(pageid)">
{{ page.pageBody }}
</div>
scope:
$scope.pageMatch = function(pageid) {
return function(page) {
return page.pageid === pageid;
};
};
Set pageid to needed value in filter:pageMatch(pageid) to display necessary page content.
function getPage (id) {
angular.forEach($scope.myBook.pages, function (page, pageIndex) {
if (page.pageId == id) {
console.log("Do something here.");
return page;
}
});
}
Otherwise . . .
$scope.myBook.pages[1];
I ended up asking the AngularJS IRC - And one of the guys put this plunker together with exactly what I was looking for.
Plnkr Example
Props to https://github.com/robwormald for the answer.
/* See PLNKR Link Above for the answer */
Related
I am building a site with Gatsby and so far I find programmatically creating pages a very handy feature. You can have a category template and have gatsby-node create pages for each content category very easily.
However, what is the recommended way to add differentiated content to that same template?
Right now I'm using ternary operators to check which category it is and add specific content based on that precise category (a few examples of content that needs to be differentiated accordingly: category intro title, SEO title and SEO description inside the SEO component based on Helmet)
inside categoryTemplate.js
const booksTitle = "Quote on books"
const songsTitle = "Quote on songs"
const travelsTitle = "Quote on travels"
const booksSeoTitle = "Books Title for SEO"
...
<CategoryIntro
title={<>
{category === "books"
? booksTitle
: category === "songs"
? songsTitle
: category === "travels"
? travelsTitle
: null
}
</>}
/>
This approach actually works, but I wonder if there's a more convenient practice that I could use? Should I store information about categories elsewhere in a json format instead of having them declared in the template file?
Thanks a lot.
I think the approach you suggested about storing that information elsewhere would make the code cleaner and easier to maintain. The template component should only be a generic template as it is intended. You shouldn't mix it with content.
How about a categories.js file?
export const categories = {
book: {
title: "Quote on books",
seoTitle: "",
},
songs: {
title: "Quote on songs",
seoTitle: "",
},
}
Import your categories.js in your template file and let it decide which category to choose via a prop:
import { categories } from "./categories.js"
// This function returns your mediaType object
function getObject(obj, mediaType) {
return obj[mediaType];
}
function MediaTemplate({ mediaType }) {
const category = getObject(mediaType);
// See this answer for how to access the object key dynamically: https://stackoverflow.com/questions/4255472/javascript-object-access-variable-property-by-name-as-string
console.log("category:");
console.log(category);
// It is only one amazing line now!
return (
<CategoryIntro title={category.title} />
);
}
I'm wondering how to handle the "no data" scenario with the Meteor's Flow Router. Let's say I want to have a route /article/:slug on which I want to render an Article (fetched by the slug field) along with related comments (separate collection). So I'll define a route like this:
FlowRouter.route('/article/:slug', {
subscriptions: function(params) {
this.register('article', Meteor.subscribe('articleSlug', params.slug));
this.register('comments', Meteor.subscribe('commentsByArticleSlug', params.slug));
},
action: function() {
FlowLayout.render('appLayout', { main: "articleDetail" });
}
});
In the template I'll display the article details & comments when subs are ready, until then I'll just display a "loading..." text.
But what if the :slug param in the URL doesn't match any article in the DB? I don't want to keep displaying the "loading..." text, I want to display some sort of "Article not found" message. How to do this?
Thanks for any advice.
UPDATE: I'd also like to point to the github discussion related to this question: https://github.com/meteorhacks/flow-router/issues/70
This could be done by your template.
Template.articleDetail.helpers ({
articleFound: articles.find().count() > 0
});
In your view, render the right stuff depends on result of articleFound.
This isn't an official answer, but it's what I'd do. Let's say this is for a blog (looks that way) you can just throw a conditional there, similar to a 404.
So
subscriptions: function(params) {
//check that this slug has an article assigned to it
var slugCheck = Meteor.subscribe('articleSlug', params.slug);
if (slugCheck != ''){
this.register('article', Meteor.subscribe('articleSlug', params.slug));
this.register('comments', Meteor.subscribe('commentsByArticleSlug', params.slug));
}
else{
this.register('article', Meteor.subscribe('articleSlug', 'missing'));
}
}
Then you'd just need to show the missing article entry.
Thanks for both answers! While testing them I came up with a third way: leave the subs in the subscriptions section and put the check to the action section - there we can do redirect if no article fit the slug.
FlowRouter.route('/article/:slug', {
subscriptions: function(params) {
this.register('article', Meteor.subscribe('articleSlug', params.slug));
this.register('comments', Meteor.subscribe('commentsByArticleSlug', params.slug));
},
action: function() {
if (Articles.find().count() > 0) {
FlowLayout.render('appLayout', { main: "articleDetail" });
}
else {
FlowRouter.go('/articlenotfound');
}
}
});
I am trying to do something that sounds simple but I can't find the solution.
My application needs to edit documents which contains pages.
Here is my model :
MyApplication.Document = DS.Model.extend({
title: DS.attr('string'),
pages: DS.hasMany('page', {async: true})
});
MyApplication.Page = DS.Model.extend({
document: DS.belongsTo('document', {async: true}),
title: DS.attr('string'),
params: DS.attr(),
objects: DS.attr()
});
And the routes :
MyApplication.Router.map(function () {
this.resource('document', {path: '/document/:document_id'});
});
MyApplication.Document = Ember.Route.extend({
model: function (params) {
return this.store.find('document', params.document_id);
}
});
When I load the document 1, the application call http://www.myserver.com/api/document/1.
The problem is that when I want to find a page of the document, it calls
http://www.myserver.com/api/pages/ID
instead of
http://www.myserver.com/api/document/1/pages/ID
Theses nested URL are important in my application.
I found different things on the subject like adding links in the JSON response :
{
"document": {
"id": "1",
"title": "Titre du document",
"pages": ["1", "2", "3"],
"links": {"pages" : "pages"}
},
But when I call for the pages, it requests http://www.myserver.com/api/document/1/pages without the id.
I also try specify the document when I ask for the page :
this.store.find("page", 1, {document:1});
Can't find a complete documentation on this subject, so if someone can explain me what's wrong, I'll be happy.
Thank.
Depends : EMBER DATA >= V1.0.0-BETA.9
The way to handle nested routes is hidden under release notes
Need to send back the links with response like this
{
"document": {
"id": 1,
"title": "Titre du document",
"links": {"pages" : "/documents/1/pages"}
}
You'll need to customize the adapter:page's buldUrl method like
MyApplication.PageAdapter = DS.RestAdapter.extend({
// NOTE: this is just a simple example, but you might actually need more customization when necessary
buildURL: function(type, id, snapshot) {
return '/documents/' + snapshot.record.get('document.id') + '/pages/' + id;
}
});
#code-jaff answer adapted to Ember 2.1.0:
// app/adapters/page.js
import DS from './application'; // I suppose you have your adapter/application.js
export default DS.RESTAdapter.extend({
buildURL: function(type, id, snapshot) {
return this.host + '/' + this.namespace + '/documents/' + snapshot.record.get('document.id') + '/pages/' + id;
}
});
Your problem likely stems from the quotes that are surrounding the IDs in your JSON. If you modify your serializer so that there are no quotes for the the IDs both around the document ID and the pages IDs, you should get the behavior that you expect. Also, you need to modify the formatting of your links to point to the relative path:
The resulting JSON should look like:
{
"document": {
"id": 1,
"title": "Titre du document",
"pages": [1, 2, 3],
"links": {"pages" : "/documents/1/pages"}
}
Please see this answer for a description of why adherence to Ember's expectations with regard to the JSON format is important and for an overview of the expected format.
Bear with me please if this sounds too simple.
I am writing a chrome extension that once the user right clicks on an object in the page, the context menu appears and there he has an option to get the ID the of clicked div-class + rate it.
I've been working on this for a few days and managed to add the extra buttons to the context menu. Once any of those buttons is clicked, the page's source is logged. Could have taken me less time to do that but it's my first time writing in javascript and I got stuck along the way.
Anyways I don't want the whole html code of the page, but the clicked div-class. I after googling it turns out that the best way to do this is by using jquery's "html" function instead of pure javascript. Problem is that it doesn't work for me.
Here is my code:
BackgroundPage (everything seems to be working fine here)
function genericOnClick(tab)
{
console.log(tab.pageUrl);
chrome.tabs.getSelected(null, function(tab)
{
chrome.tabs.sendMessage(tab.id, {action: "getSource"}, function(source) {
console.log(source);
});
});
}
// Create a parent item and two children.
var parent1 = chrome.contextMenus.create({"title": "Rank Trigger", "contexts":["all"]});
var child1 = chrome.contextMenus.create
(
{"title": "Rank 1", "contexts":["all"], "parentId": parent1, "onclick": genericOnClick}
);
var child2 = chrome.contextMenus.create
(
{"title": "Rank 2", "contexts":["all"], "parentId": parent1, "onclick": genericOnClick}
);
var child3 = chrome.contextMenus.create
(
{"title": "Rank 3", "contexts":["all"], "parentId": parent1, "onclick": genericOnClick}
);
ContentPage: (problem here)
chrome.extension.onMessage.addListener(function(request, sender, callback)
{
if (request.action == "getSource")
{
// callback(document.documentElement.outerHTML); //here i can get the page's whole source, I only want the clicked div class
callback($('div class).html(); <--- something wrong here
}
});
Manifest file:
{
"name": "x",
"description": "x",
"version": "0.1",
"permissions": ["contextMenus", "tabs"],
"content_scripts":
[
{
"matches": ["http://*/*","https://*/*"],
"js": ["jquery.js", "content.js"],
"run_at": "document_end"
}
],
"background":
{
"scripts": ["background.js"]
},
"manifest_version": 2
}
Any ideas? Again I know that my questions seems simple but I am self teaching myself all this, and for some reason I finding web-programming a little harder than application programming.
Much thanks.
EDIT: Just for clarification the html source I want is this:
<div class="story reviewed-product-story" data-story-id="488481648"....</div>.
I want this div class, so that I can parse it to get the data-story-id. If there is a way to get the ID without getting the div class first then that would also work (and maybe even preferable)
EDIT: Thanks to Adeneo, I can now get the ID of the first div-class in the page's source, but not the clicked one. A step forward on what I had but not exactly what I need.
Maybe I have to get the source of clicked div before using $('.story').data('story-id'). Any suggestions?
I'm guessing it's supposed to be:
chrome.extension.onMessage.addListener(function(request, sender, callback) {
console.log('message listener');
if (request.action == "getSource") {
callback( $('.story').data('story-id') );
}
});
Here is the basic setup, which has a default noemployee.html partial: as the ng-view
Index.html content:
<div id="container" ng-controller="EmployeeCtrl">
<!-- Side Menu -->
<span id="smenuSpan">
<ul id="thumbList">
<li ng-repeat="employee in employees | filter:categories">
<img class="smallImage" ng-src="content/app/images/{{employee.image}}" alt="{{employee.description}}">
</li>
</ul>
</span>
<!-- Content -->
<span id="contentSpan">
<div ng-view></div>
</span>
</div>
My Route Provider:
var EmployeeModule = angular.module('EmployeeModule', [], function($routeProvider, $locationProvider) {
$routeProvider.when('/', { templateUrl: 'content/app/partials/noemployee.html', controller: EmployeeModule.EmployeeCtrl });
$routeProvider.when('Employee/:id', { templateUrl: 'content/app/partials/employee.html', controller: EmployeeModule.EmployeeCtrl });
$routeProvider.otherwise({ redirectTo: '/' });
$locationProvider.html5Mode(true);
My Controller:
function EmployeeCtrl($scope, $http, $routeParams, $timeout) {
$scope.employees = [
{ "id": 1, "category": "ones", "image": "person1.jpg", "description": "person 1 description", name:"Jane Smith" },
{ "id": 2, "category": "twos", "image": "person2.jpg", "description": "person 2 description", name: "Mark Sharp" },
{ "id": 3, "category": "threes", "image": "person3.jpg", "description": "person 3 description", name: "Kenny Suave" },
{ "id": 4, "category": "fours", "image": "person4.jpg", "description": "person 4 description", name: "Betty Charmer" },
{ "id": 5, "category": "fives", "image": "person5.jpg", "description": "person 5 description", name: "John Boss" }
];
$scope.employeesCategories = [];
$scope.currentEmployee = {};
$scope.params = $routeParams;
$scope.handleEmployeesLoaded = function (data, status) {
//$scope.images = data;
// Set the current image to the first image in images
$scope.currentEmployee = _.first($scope.employees);
// Create a unique array based on the category property in the images objects
$scope.employeeCategories = _.uniq(_.pluck($scope.employees, 'category'));
}
$scope.fetch = function () {
$http.get($scope.url).success($scope.handleEmployeesLoaded);
};
$scope.setCurrentEmployee = function (employee) {
$scope.currentEmployee = employee;
};
// Defer fetch for 1 second to give everything an opportunity layout
$timeout($scope.fetch, 1000);
}
Observations:
At present, if I click on any employee, no 'Employee/??' is added to the address bar path [ which isn't a crime to me], however, the main content div does not change the partial to the employee.html.
If I comment out "$locationProvider.html5Mode(true);", the default localhost is now "http://localhost:31219/#/" and when I click on any employee the address bar shows 'http://localhost:31219/Employee/1', and the page is navigated away to a 404 error page.
I know I am bastardizing something here that the solution is so simple it escapes me.
Goals:
I really would like to avoid hash tags in my address bar.
It would be nice but no req that the employee/id not show up in the address bar but I suspect the partial cannot change w/o it. and, naturally
I want the partial to change to the 'employee.html" page when an employee is clicked.
Does anyone see where I am going wrong with this code?
Thanks in Advance!
Solution:
I needed to put '#/' in the img href --> href="#/Employee/{{employee.id}}"
Comment out
'$locationProvider.html5Mode(true);'
As a side note, I sure wish I knew how to get this to work w/o those pesky hash tags. Any ideas anyone?
In order to use html5mode, your server has to serve up the main app index file for otherwise invalid routes.
So, for example, if your server side code handles CRUD operations on paths like: /api/employees, /api/employees/:id, etc...
and it serves up static content, images, html, css, js, etc.
For any other request, that would otherwise be a 404, it should, instead of responding with a 404, respond with a 200 code, and serve up the index.html file.
This way any non static and non server side route gets handled by the angular app.
This is mentioned in the Angular guide on this page: http://docs.angularjs.org/guide/dev_guide.services.$location
Note the 'server side' comment at the end:
Html link rewriting
When you use HTML5 history API mode, you will need
different links in different browsers, but all you have to do is
specify regular URL links, such as: link
When a user clicks on this link:
In a legacy browser, the URL changes to /index.html#!/some?foo=bar
In a modern browser, the URL changes to /some?foo=bar In cases like the
following, links are not rewritten; instead, the browser will perform
a full page reload to the original link.
Links that contain target element
Example: link
Absolute links that go to a different domain
Example: link
Links starting with '/' that lead to a different base path when base is defined
Example: link
Server side
Using this mode requires URL rewriting on server side, basically you have to rewrite
all your links to entry point of your application (e.g. index.html)
This was the problem:
<img class="smallImage" ng-src="content/app/images/{{employee.image}}" alt="{{employee.description}}">
Solution:
I needed to put '#/' in the img href --> href="#/Employee/{{employee.id}}"
Comment out '$locationProvider.html5Mode(true);'
As a side note, I sure wish I knew how to get this to work w/o those pesky hash tags. Any ideas anyone?