"position" property required for ItemList with Product list items? - schema.org

I have a problem: Google’s Structured Data Testing Tool gives me an error:
Tag position doesn't exist. It's required.
I add it to the markup. Than I get this error:
Position property is not valid for an object of type Product
Here is my markup:
<table id="sale_table" itemscope="" itemtype="http://schema.org/ItemList">
<tbody>
<tr itemprop="itemListElement" itemscope="" itemtype="http://schema.org/Product">
<td class="sale_art_td" itemprop="productID">10496278</td>
<td class="sale_brand_td" itemprop="brand"><span itemprop="name ">--</span></td>
<td class="sale_name_td" itemprop="name">10496278 / Крышка трамблера Daewoo Nexia,Espero DD</td>
<td class="sale_am_td">1.00</td>
<td class="sale_price_td" itemprop="offers" itemscope="" itemtype="http://schema.org/Offer"><meta itemprop="priceCurrency" content="RUR"><span itemprop="price">341.50</span></td>
<td class="sale_buy_td">Купить<!--<img src="/upload/badge/sale_cart.png" />--></td>
<td class="hidden">
<meta itemprop="url" content="/partsearch/?q=10496278">
<span itemprop="description">Распродажа: 10496278 / Крышка трамблера Daewoo Nexia,Espero DD по цене 341.50</span>
</td>
</tr>
<tr itemprop="itemListElement" itemscope="" itemtype="http://schema.org/Product">
<td class="sale_art_td" itemprop="productID">76202sx0a12</td>
<td class="sale_brand_td" itemprop="brand"><span itemprop="name ">HONDA</span></td>
<td class="sale_name_td" itemprop="name">76202SX0A12</td>
<td class="sale_am_td">1.00</td>
<td class="sale_price_td" itemprop="offers" itemscope="" itemtype="http://schema.org/Offer"><meta itemprop="priceCurrency" content="RUR"><span itemprop="price">704.00</span></td>
<td class="sale_buy_td">Купить<!--<img src="/upload/badge/sale_cart.png" />--></td>
<td class="hidden">
<meta itemprop="url" content="/partsearch/?q=76202sx0a12">
<span itemprop="description">Распродажа: 76202SX0A12 по цене 704.00</span>
</td>
</tr>
</tbody>
</table>

This is not an error with your code. It just means that Google won’t display a certain Rich Snippet (or a similar feature) unless you provide this property.
However, the position property is not defined for the Product type, so this does not make any sense.
It seems that this is a new structured data feature from Google, which is not documented yet, as it links to a 404 page: List Page Carousels. Maybe it’s a work in progress and they didn’t mean to publish the check in their Testing Tool yet.
So I’d simply ignore this for now.

From my testing malefique is onto the right solution.
This code fully validates using the Structured Data testing tool:
{
"#context": "http://schema.org",
"#type": "ItemList",
"itemListOrder": "http://schema.org/ItemListOrderDescending",
"itemListElement": [
{
"#type": "ListItem",
"position": 1,
"item": {
"#type": "Product",
"name": "My product",
"url": "www.example.com",
"offers": {
"#type": "Offer",
"availability": "http://schema.org/InStock",
"price": "100.00",
"priceCurrency": "AUD"
}
}
}
]
}

I guess this is an implementation error on Google's side caused by non explicit documentation:
https://schema.org/itemListElement clearly states
Existing entities are best for a simple, unordered list of existing things in your data. ListItem is used with ordered lists when you want to provide additional context about the element in that list or when the same item might be in different places in different lists.
Note: The order of elements in your mark-up is not sufficient for indicating the order or elements. Use ListItem with a 'position' property in such cases.
At the same time is says Values expected to be one of these types are:
ListItem
Text
Thing
=> This means implicitly, that the position element can only be required for sorted lists, which in turn demand that the Thing element is contained inside a ListItem element, which offers the itemprop position.
It also means implicitly that if the ItemListElement is Text or Thing, the list should be considered Unordered.
This is the only way the documentation makes sense. I assume that implicit connection was missed.
So I guess the appropriate action is to file a bug report for the Structured Data Testing Tool and live with the warnings for now or nest the Product inside a ListItem element as a workaround.

I found http://schema.org/itemListElement
you have to specify the position like so <meta itemprop="position" content="1" />
There is an example at the bottom of the page.

try this
'#type': 'ListItem',
'position': 1,
'item':{
'#type': 'Product',
... product props
}

The fix is to stuff your product into a ListItem's item property. The ListItem gets the position property. Once you do that, it passes Google's SDTT.
NOTE: It is invalid to have a product offer on more than 1 URL/URI.
Build out a page (unique URL) for each product offer and that's where you put your structured data / schema.org product offer data; OR, put all your product offers on a single page (URL/URI) - never both. Otherwise, you'll get error All values provided for url must point to the same page.

Related

Selenium IDE Click on a button in a row identified by text

I am trying to get my script to click on a button called Enter Response located in the same row as a specific text (SID). I am able to locate both separately but can't seem to make both work at the same time.
The table is dynamic so I am using the SID, finding it's row and then want to click on it's response button. I.E. find text '123456' & click on 'Enter Response' found in the same row.
I tried this but am getting an error locator not found:
//tr/td/a[#class='title-abbr' and text()='123456']/following-sibling::td/a[text()='Enter Response']
Table Row Headers:
Title/Source/Source ID/SID/Create Date/(Enter Response button)/Form Type
<tr>
<td class="t-Report-cell" headers="TITLE_ABBR">this is my title</td>
<td class="t-Report-cell" headers="SOURCE_NAME">source1</td>
<td class="t-Report-cell" headers="SOURCE_NUMBER">142417</td>
<td style="background-color: rgb(13, 13, 13);" class="t-Report-cell" headers="SID_ABBR">
123456
</td>
<td class="t-Report-cell" headers="TRANSACTION_DATE">07/28/2016</td>
<td style="background-color: rgb(13, 13, 13);" class="t-Report-cell" headers="LINK" align="center">
<a style="background-color: rgb(0, 255, 255);" class="response-btn" href="f?p=58117:50:27077013481519::NO::P50_TRIGGER_ID:321860">Enter Response</a>
</td>
<td class="t-Report-cell" headers="FORM_TYPE">Questions</td>
</tr>
Any help would be greatly appreciated!
if I am looking right, you should first get to the parent node before trying to get to the next sibling....because "a" element has no sibling, so basically something like that:
//tr/td/a[#class='title-abbr' and text()='123456']/../following-sibling::td/a[text()='Enter Response']
You could also just use following-sibling::td[2] instead of selecting by text in the second part of xpath, but maybe you need it for some reason.
Btw some tips:
If the element could have multiple style classes, you will have to use contains
The same applies to text, beware of spaces etc. you should trim the text or use contains to avoid wrong selectors

Angular 2 Dynamic Nested Form

Basically I want to create a dynamic form with nested objects like the picture below:
Pay offs are in an array on the model
We should be able to add/remove pay offs as needed.
The form should sync underlying form controls and model
The number of pay offs is arbitrary and should be loaded into the form from the model
There are no working examples that I could find as how to do this in Angular 2, although this was really easy to do in Angular 1.
Below is my original question, I've since updated it for clarification (see above):
First I just wanted to point out that I'm aware that a new version of Angular 2 rc.2 has just been released a few days ago. So the code for creating a dynamic, nested form may have changed some but there's not enough documentation to figure this out.
In the latest version(s) of Angular 2 (I'm currently using rc.1 but planning to update to rc.2) I need to create a form like this (pseudo-code of view):
<form [ngFormModel]="form" (ngSubmit)="onSubmit()">
<input type="text" ngControl="name">
<div *ngFor="let expense for expenses; let i = index;" control-group="expenses">
<input type="text" ngControl="expense.amount" [(ngModel)]="myModel.expenses[i].amount">
<input type="checkbox" ngControl="expense.final" [(ngModel)]="myModel.expenses[i].final">
</div>
<a class="button" (click)="addExpenseControl()">Add</a>
<a class="button" (click)="deleteExpenseControl()">Delete</a>
</form>
So the pseudo-code above won't work but to be honest because of lack of documentation I can't figure out how to wire something like this up. There's a few tutorials about nested ControlGroup but this won't fit the case here since we need to be able to dynamically add and remove control groups, and also I need to be able to sync them with a model.
I found this plunkr here provided by Angular team which allows adding of Controls to a form--but this is not adding/removing a ControlGroup, rather it's using ControlArray and I'm not sure if that applies here?
I'm very familiar with using the newer model-based Angular 2 forms however I'm grasping for resources in order to properly nest them (dynamically!), and tie this nested data into the main form model. How would I refer to nested controls in the view? Is the pseudo-code above even close? I'd post code from my controller but honestly I wouldn't know where to start when it comes to the nested expenses (ControlGroup ??) above...
I had to figure this out on my own because it seems that forms are still changing in Angular 2 and I've not seen any other examples similar to this (although it seems like a very common use-case).
Here is a plunkr of working example using Angular2 RC3.
I am using updated Angular 2 form code from this document.
app.component.ts (contains the form):
import { Component } from '#angular/core';
import {REACTIVE_FORM_DIRECTIVES, FormControl, FormGroup, FormArray} from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: 'app/app.html',
directives: [REACTIVE_FORM_DIRECTIVES],
providers: []
})
export class AppComponent {
form: FormGroup;
myModel:any;
constructor() {
// initializing a model for the form to keep in sync with.
// usually you'd grab this from a backend API
this.myModel = {
name: "Joanna Jedrzejczyk",
payOffs: [
{amount: 111.11, date: "Jan 1, 2016", final: false},
{amount: 222.22, date: "Jan 2, 2016", final: true}
]
}
// initialize form with empty FormArray for payOffs
this.form = new FormGroup({
name: new FormControl(''),
payOffs: new FormArray([])
});
// now we manually use the model and push a FormGroup into the form's FormArray for each PayOff
this.myModel.payOffs.forEach(
(po) =>
this.form.controls.payOffs.push(this.createPayOffFormGroup(po))
);
}
createPayOffFormGroup(payOffObj) {
console.log("payOffObj", payOffObj);
return new FormGroup({
amount: new FormControl(payOffObj.amount),
date: new FormControl(payOffObj.date),
final: new FormControl(payOffObj.final)
});
}
addPayOff(event) {
event.preventDefault(); // ensure this button doesn't try to submit the form
var emptyPayOff = {amount: null, date: null, final: false};
// add pay off to both the model and to form controls because I don't think Angular has any way to do this automagically yet
this.myModel.payOffs.push(emptyPayOff);
this.form.controls.payOffs.push(this.createPayOffFormGroup(emptyPayOff));
console.log("Added New Pay Off", this.form.controls.payOffs)
}
deletePayOff(index:number) {
// delete payoff from both the model and the FormArray
this.myModel.payOffs.splice(index, 1);
this.form.controls.payOffs.removeAt(index);
}
}
Notice above that I manually push new FormGroup objects into the form.controls.payOffs array, which is a FormArray object.
app.html (contains form html):
<form (ngSubmit)="onSubmit()" [formGroup]="form">
<label>Name</label>
<input type="text" formControlName="name" [(ngModel)]="myModel.name" placeholder="Name">
<p>Pay Offs</p>
<table class="simple-table">
<tr>
<th>Amount</th>
<th>Date</th>
<th>Final?</th>
<th></th>
</tr>
<tbody>
<tr *ngFor="let po of form.find('payOffs').controls; let i = index">
<td>
<input type="text" size=10 [formControl]="po.controls.amount" [(ngModel)]="myModel.payOffs[i].amount">
</td>
<td>
<input type="text" [formControl]="po.controls.date" [(ngModel)]="myModel.payOffs[i].date">
</td>
<td>
<input type="checkbox" [formControl]="po.controls.final" [(ngModel)]="myModel.payOffs[i].final">
</td>
<td>
<button (click)="deletePayOff(i)" style="color: white; background: rgba(255, 0, 0, .5)">x</button>
</td>
</tr>
</tbody>
<tr>
<td colspan="4" style="text-align: center; padding: .5em;">
<button (click)="addPayOff($event)" style="color: white; background: rgba(0, 150, 0, 1)">Add Pay Off</button>
</td>
</tr>
</table>
</form>
In the html form I link the form to the model on the inputs with statements like so:
... [formControl]="po.controls.amount" [(ngModel)]="myModel.payOffs[i].amount" ...

AngularJS Calculating product prices with multiple select options

Summary:
Users should be choosing their desired products in a form environment. Every product has a core price and multiple additional options available which change the price when selected.
Products and options are shown in two SELECT fields which are getting populated like this:
$scope.products = [
{
name:'Product A',
cost:5,
options: [{name:"Option 1", value:10}]
},
{
name:'Product B',
cost:10,
options: [{name:"Option 1", value:10},{name:"Option 2", value:15}]
}
];
$scope.cart = {
items: [{
qty: 1,
}]
};
and
<tr ng:repeat="item in cart.items">
<td>
<div class="type-select">
<select ng-model="item.product" ng-options="p.name for p in products"></select>
</div>
</td>
<td>
<div class="type-select">
<select ng-model="item.option" ng-options="o for o in item.product.options.name" ng- disabled="!checked">
</div>
</td>
<td>
<input ng:model="item.qty" value="1" size="4" ng:required="" ng:validate="integer" class="ng-pristine ng-valid ng-valid-required input-mini">
</td>
<td>
{{calculate()}}
</td>
</tr>
The options select stays empty. Why?
How can i calculate this the angular way? (There will be multiple lines of product possible)
You might find my example app airquotes a good reference: https://github.com/JohnMunsch/airquotes
It's an AngularJS app I wrote for a t-shirt site and it demonstrates generating quotes on the fly given a set of different values the user may set that can affect the price (such as darker colors having a surcharge because more ink has to be used when screen printing them and xxl shirts have a price premium).
It sounds like it's a good match for the kind of thing you're trying to build here.
<select ng-model="item.product" ng-options="p as p.name for p in products">
</select>
...
<select ng-model="item.option" ng-options="o as o.name for o in
item.product.options" ng-disabled="!checked"></select>
...
<td>
{{calculate(item)}}
</td>
Controller:
$scope.calculate = function(item){
/* return the calculated cost */
}

AngularJS rows are displayed with empty data

I am testing out angularJS.
In app.js I have
function ListCtrl($scope, Restangular) {
Restangular.all("employee").getList().then(function(employee) {
$scope.employee = employee;
console.log($scope.employee.emp);
});
}
and in html I have
<table class="table table-striped table-condensed">
<thead>
<tr>
<th>Emp No</th>
<th>Name</th>
<th><i class="icon-plus-sign"></i></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="employee | filter:search | orderBy:'ename'">
<td>{{employee.empno}}
</td>
<td>{{employee.ename}}</td>
<td>
<i class="icon-pencil"></i>
</td>
</tr>
</tbody>
</table>
Problem I am facing is there are empty rows being displayed with no data being displayed.
What could be the reason for this?
Edit 1
JSON returned from server
{"emp":[{"empno":"7369","ename":"SMITH","hiredate":
"1980-12-17T00:00:00+03:00","job":"CLERK","mgr":"7902","sal":"800"},
{"comm":"300","empno":"7499","ename":"ALLEN","hiredate":
"1981-02-20T00:00:00+03:00","job":"SALESMAN","mgr":"7698","sal":"1600"},
{"comm":"500","empno":"7521","ename":"WARD","hiredate":
"1981-02-22T00:00:00+03:00","job":"SALESMAN","mgr":"7698","sal":"1250"},
{"empno":"7566","ename":"JONES","hiredate":
"1981-04-02T00:00:00+03:00","job":"MANAGER","mgr":"7839","sal":"2975"},
{"comm":"1400","empno":"7654","ename":"MARTIN","hiredate":
"1981-09-28T00:00:00+03:00","job":"SALESMAN","mgr":"7698","sal":"1250"},
{"empno":"7698","ename":"BLAKE","hiredate":
"1981-05-01T00:00:00+03:00","job":"MANAGER","mgr":"7839","sal":"2850"},
{"empno":"7782","ename":"CLARK","hiredate":
"1981-06-09T00:00:00+03:00","job":"MANAGER","mgr":"7839","sal":"2450"},
{"empno":"7788","ename":"SCOTT","hiredate":
"1987-04-19T00:00:00+03:00","job":"ANALYST","mgr":"7566","sal":"3000"},
{"empno":"7839","ename":"KING","hiredate":
"1981-11-17T00:00:00+03:00","job":"PRESIDENT","sal":"5000"},
{"comm":"0","empno":"7844","ename":"TURNER","hiredate":
"1981-09-08T00:00:00+03:00","job":"SALESMAN","mgr":"7698","sal":"1500"}]}
console log from chrome browser
[Object, Object, Object, Object, Object, Object, Object, Object,
Object, Object, Object, Object, Object, Object, route: "employee",
getRestangularUrl: function, addRestangularMethod: function, one:
function, all: function…]
0: Object
empno: "7369"
ename: "SMITH"
hiredate: "1980-12-17T00:00:00+03:00"
job: "CLERK"
mgr: "7902"
sal: "800"
Based on the JSON you've included it looks like $scope.employee should contain a one key called "emp", which is what you print to the console. You might need to change your ng-repeat to work with that.
Also, I'm unfamiliar with the form of your ng-repeat expression. I believe they are supposed to follow a form similar to "something in somethings" so in this case instead of just employee you may want that to be employee in employee.emp.
In a more general sense, the Angular Batarang plugin for Chrome is infinitely helpful for solving these sorts of problems.

Makumba - select first value in a subset

I'm trying to select the latest activity organised by an LBG for all LBGs.
currently my code looks like this:
<mak:list from="r2.lbgs l" orderBy="l.lbg.name" where="l.toDate = nil and l.lbg.status = 2">
<tr>
<td><mak:value expr="l.lbg.name" /></td>
<td>
<mak:list from="best.johnny.Activity a" where="a.lbg = l.lbg" orderBy="a.end desc">
<c:if test="${mak:count()==1}">
<mak:value expr="a.season.name" />
</c:if>
</mak:list>
</td>
<td>
<mak:list from="best.johnny.Activity a" where="a.lbg = l.lbg and a.isLearningEventStamped = 0" orderBy="a.end desc">
<c:if test="${mak:count()==1}">
<mak:value expr="a.season.name" />
</c:if>
</mak:list>
</td>
</tr>
</mak:list>
I wanted to ask if makumba is "clever" enough and retrieves only the first entry, or if the whole set get enumerated. First case would be awesome, otherwise is there any more sufficient way of doing this? - I couldn't find anything.
Thanks
Peter
Unfortunately this is, for now, the only way since you have a nested list. As it says in the documentation there is a limit attribute, but it only works for the first list.