apcdatanalytics / rets-rabbit
以简单直观的方式在你的工艺网站上展示房地产列表。
Requires
- ext-json: *
- apcdatanalytics/rets-rabbit-cms-core: ^2.0
- craftcms/cms: ^3.0.0
This package is auto-updated.
Last update: 2024-09-18 02:39:20 UTC
README
此插件允许您连接到Rets Rabbit API(v2),以便以干净直观的方式展示您的列表。
安装
composer require apcdatanalytics/rets-rabbit
要求
Rets Rabbit插件需要至少php 7.0,符合Craft 3的最小PHP要求。
文档
您可以通过PropertiesVariable & OpenHousesVariable与Rets Rabbit API交互。
开放房屋
- craft.retsRabbit.openHouses.find - 单个开放房屋查找
- craft.retsRabbit.openHouses.query - 运行原始RESO查询
find(int $id, object $resoParams, bool $useCache = false, int $cacheDuration)
$id - 您要从API获取的开放房屋的MLS ID。
$resoParams - 您可以传递有效的RESO参数以帮助过滤单个开放房屋的API结果。通过使用$select参数选择您将需要的字段,这可以帮助加快响应时间。
$useCache - 指定是否要缓存结果。
$cacheDuration - 指定您希望结果缓存的秒数。默认为一个小时。
{% set viewModel = craft.retsRabbit.openHouses.find('OpenHouseId', {'$select': 'OpenHouseId'}, true) %}
{% if viewModel.hasErrors() %}
{# An error occurred, let the user know #}
{% elseif not viewModel.hasData() %}
{# No data returned from request #}
{% else %}
{% set openHouse = viewModel.data %}
{{openHouse.OpenHouseId}}
{% endif %}
query(object $resoParams, bool $useCache = false, int $cacheDuration)
$resoParams - 您可以传递有效的RESO参数以帮助过滤单个开放房屋的API结果。通过使用$select参数选择您将需要的字段,这可以帮助加快响应时间。
$useCache - 指定是否要缓存结果。
$cacheDuration - 指定您希望结果缓存的秒数。默认为一个小时。
{% set viewModel = craft.retsRabbit.openHouses.query({
'$select': 'OpenHouseId, OpenHouseDate, OpenHouseStartTime, OpenHouseEndTime',
'$top': 12
}) %}
{% if viewModel.hasErrors() %}
{# An error occurred #}
{% elseif not viewModel.hasData() %}
{# No data returned in response #}
{% else %}
{% set openHouses = viewModel.data %}
{% for openHouse in openHouses %}
<div class="card">
<div class="card-header">
{{openHouse.OpenHouseId}}
</div>
<div class="card-content">
<div>
<date>{{ openHouse.OpenHouseDate }}</date>
</div>
<div>
<date>{{ openHouse.OpenHouseStartTime }}</date>
</div>
<div>
<date>{{ openHouse.OpenHouseEndTime }}</date>
</div>
</div>
</div>
{% endfor %}
{% endif %}
属性
- craft.retsRabbit.properties.find - 单个列表查找
- craft.retsRabbit.properties.query - 运行原始RESO查询
- craft.retsRabbit.properties.search - 使用搜索表单中的保存查询执行搜索
find(int $id, object $resoParams, bool $useCache = false, int $cacheDuration)
$id - 您要从API获取的属性的MLS ID。
$resoParams - 您可以传递有效的RESO参数以帮助过滤单个列表的API结果。通过使用$select参数选择您将需要的字段,这可以帮助加快响应时间。
$useCache - 指定是否要缓存结果。
$cacheDuration - 指定您希望结果缓存的秒数。默认为一个小时。
{% set viewModel = craft.retsRabbit.properties.find('123abc', {'$select': 'ListingId, ListPrice'}, true) %}
{% if viewModel.hasErrors() %}
{# An error occurred, let the user know #}
{% elseif not viewModel.hasData() %}
{# No data returned from request #}
{% else %}
{% set listing = viewModel.data %}
{{listing.ListingId}}
{% endif %}
query(object $resoParams, bool $useCache = false, int $cacheDuration)
$resoParams - 您可以传递有效的RESO参数以帮助过滤单个列表的API结果。通过使用$select参数选择您将需要的字段,这可以帮助加快响应时间。
$useCache - 指定是否要缓存结果。
$cacheDuration - 指定您希望结果缓存的秒数。默认为一个小时。
{% set viewModel = craft.retsRabbit.properties.query({
'$select': 'ListingId, ListPrice, PublicRemarks, StateOrProvince, City',
'$filter': 'ListPrice ge 150000 and ListPrice le 175000 and BedroomsTotal ge 3',
'$orderby': 'ListPrice',
'$top': 12
}) %}
{% if viewModel.hasErrors() %}
{# An error occurred #}
{% elseif not viewModel.hasData() %}
{# No data returned in response #}
{% else %}
{% set listings = viewModel.data %}
{% for listing in listings %}
<div class="card">
<div class="card-header">
{{listing.ListingId}}
</div>
<div class="card-content">
{{listing.ListPrice}}
</div>
</div>
{% endfor %}
{% endif %}
search(int $id, object $overrides, bool $useCache = false, int $cacheDuration)
$id - 通常从URL段拉取的保存搜索参数的id。
$overrides - 您可以传递以下RESO参数以帮助定制查询搜索:$select, $orderby, $top。
$useCache - 指定是否要缓存结果。
$cacheDuration - 指定您希望结果缓存的秒数。默认为一个小时。
{# Results URL (for example): /search/results/4 #}
{% set searchId = craft.app.request.getSegment(3) %}
{% if not craft.retsRabbit.searches.exists(searchId) %}
{% redirect '404' %}
{% endif %}
{% set perPage = 12 %}
{% set viewModel = craft.retsRabbit.properties.search(searchId, {
'$top': perPage,
'$orderby': 'ListPrice desc'
}, true) %}
{% if viewModel.hasErrors() %}
{# An error occurred #}
{% elseif not viewModel.hasData() %}
{# Not results returned from request #}
{% else %}
{% set results = viewModel.data %}
{% for listing in results %}
{# Show listing data #}
{% endfor %}
{% endif %}
注意:如果您想分页搜索结果,您将需要使用我们的特殊
rrPaginate标签。
搜索表单
在某个时候,您的网站将需要有一个搜索表单,用户可以输入搜索条件。我们为您创建了搜索HTML的标记DSL,这将允许您为用户提供漂亮的表单。
必填字段
您的搜索表单必须有两个输入。
actionInput("rets-rabbit/properties/search)redirectInput("search/results/{searchId}")
注意:您的
redirect输入必须包含{searchId}术语,以便处理表单POST的控制器端点可以将您重定向到包含保存搜索ID的结果页面。
我们相信以下三种搜索类型可以覆盖大多数搜索表单使用案例。
- 单个字段用于单个值
- 单个字段用于多个值
- 多个字段用于单个值
搜索表单DSL
接下来,让我们深入了解创建搜索表单。一般来说,我们的标记DSL遵循以下简单模式
<input name="{fieldName}(operator)" value="">.
单个字段 - 单个值
<input name="StateOrProvince(eq)" value="">
这将创建一个查询条件,类似于以下内容
$filter = StateOrProvince eq {value}
单个字段 - 多个值
{% set exteriorAmenities = ['Backyard', 'Pond', 'Garden'] %}
<label class="label">Exterior Features</label>
{% for feature in exteriorAmenities %}
<div class="control">
<label class="checkbox">
<input type="checkbox" name="rr:ExteriorFeatures(contains)[]" value="{{feature}}">
{{feature}}
</label>
</div>
{% endfor %}
这将创建一个查询条件,类似于以下内容
$filter = (contains(ExteriorFeatures, {value1}) or contains(ExteriorFeatures, {value2})))
多个字段 - 单个值
<input name="rr:StateOrProvince|City|PostalCode(contains)" class="input" placeholder="City, State, Zip..." type="text">
这将创建一个查询条件,类似于以下内容
$filter = (contains(StateOrProvince, {value}) or contains(City, {value}) or contains(PostalCode, {value}))
注意:默认情况下,每个输入都被视为一个独立的{and}条件,这些条件被连接起来以创建一个有效的RESO查询。
示例搜索表单
以下示例包含将生成以下功能的标记
- 在字段:州/省、城市、邮政编码上运行包含搜索
- 在ListPrice上运行范围搜索(ge和/或le)
- 在字段:BathroomsFull和BedroomsTotal上运行范围搜索(ge)
- 在外部特征上运行多值包含搜索
- 在内部特征上运行多值包含搜索
<form method="POST" action=""> {{csrfInput()}} <input type="hidden" name="action" value="retsRabbit/properties/search"> <input type="hidden" name="redirect" value="search/results/{searchId}"> <div class="field"> <div class="control has-icons-left"> <input class="input" placeholder="City, State, Zip..." type="text" name="rr:StateOrProvince|City|PostalCode(contains)"> <span class="icon is-left"> <i class="fa fa-search"></i> </span> </div> </div> <div class="field is-horizontal"> <div class="field-body"> <div class="field"> <div class="control is-expanded"> <div class="select is-fullwidth"> <select name="rr:ListPrice(ge)"> <option value="">Min Price</option> {% for price in range(30000, 300000, 10000) %} <option value="{{price}}">{{price | currency('USD', true)}}</option> {% endfor %} </select> </div> </div> </div> <div class="field"> <div class="control is-expanded"> <div class="select is-fullwidth"> <select name="rr:ListPrice(le)"> <option value="">Max Price</option> {% for price in range(30000, 300000, 10000) %} <option value="{{price}}">{{price | currency('USD', true)}}</option> {% endfor %} </select> </div> </div> </div> </div> </div> <div class="field is-horizontal"> <div class="field-body"> <div class="field"> <div class="control has-icons-left is-expanded"> <div class="select is-fullwidth"> <select name="rr:BathroomsFull(ge)"> <option value>Bathrooms</option> {% for val in 0..7 %} <option value="{{val}}">{{val}}+</option> {% endfor %} </select> </div> <span class="icon is-left"> <i class="fa fa-bath"></i> </span> </div> </div> <div class="field"> <div class="control has-icons-left is-expanded"> <div class="select is-fullwidth"> <select name="rr:BedroomsTotal(ge)"> <option value>Bedrooms</option> {% for val in 0..7 %} <option value="{{val}}">{{val}}+</option> {% endfor %} </select> </div> <span class="icon is-left"> <i class="fa fa-bed"></i> </span> </div> </div> </div> </div> <div class="field is-horizontal"> <div class="field-body"> <div class="field"> <label class="label">Exterior Features</label> {% for feature in exteriorAmenities %} <div class="control"> <label class="checkbox"> <input type="checkbox" name="rr:ExteriorFeatures(contains)[]" value="{{feature}}"> {{feature}} </label> </div> {% endfor %} </div> <div class="field"> <label class="label">Interior Features</label> {% for feature in interiorAmenities %} <div class="control"> <label class="checkbox"> <input type="checkbox" name="rr:InteriorFeatures(contains)[]" value="{{feature}}"> {{feature}} </label> </div> {% endfor %} </div> </div> </div> <button class="button is-success" type="submit"> <span class="icon"> <i class="fa fa-search"></i> </span> <span>{{'Submit'|t}}</span> </button> </form>
我们在这个示例中使用了Bulma.io,但上述标记将生成类似以下的内容。
搜索分页
由于Rets Rabbit插件从外部数据源获取数据,因此无法使用本机Craft分页标签。我们仍然认为拥有分页结果的能力非常重要,因此我们创建了一个特殊的rrPaginate标签,它在许多方面都类似于本机的paginate标签。
{% rrPaginate searchCriteria as pageInfo, viewModel %}
参数
- searchCriteria -
SearchCriteriaModel的一个实例 - pageInfo - 与本机的
pagination标签一样,为craft\web\twig\variables\Paginate - viewModel - 包含可能的搜索结果或API错误的一个视图模型实例
搜索条件
与原生paginate标签相比,我们的rrPaginate标签的主要区别是它期望第一个参数是一个SearchCriteriaModel。您可以通过以下方式获取搜索条件模型的实例。
{% set criteriaModel = craft.retsRabbit.searches.criteria() %}
一旦您有了条件模型的实例,您可以通过在该criteriaModel对象上链式调用方法以流畅的方式构建查询。
{#
# You must call the forId() and limit() methods for the pagination to work correctly.
#}
{% set criteria = criteriaModel
.forId(searchId)
.select('ListPrice', 'PublicRemarks', 'BathroomsFull', 'BedroomsTotal', 'ListingId', 'photos')
.orderBy('ListPrice', 'desc')
.limit(24)
.countBy('exact')
%}
方法:SearchCriteriaModel有以下方法可用于构建分页搜索查询。
- forId($searchId) - (必需)传入搜索ID,通常来自URL
- select(...$fields) - 传入您希望从API中获取的字段列表
- limit($limit) - (必需)每页结果数量
- orderBy($field, $dir) - 排序结果
- countBy($cacheType) - 指定API运行的总结果查询类型。有效值是'exact'或'estimated'。默认使用'estimated'。
完整示例
{% set searchId = craft.app.request.getSegment(3) %}
{% if not craft.retsRabbit.searches.exists(searchId) %}
{% redirect '404' %}
{% endif %}
{% set criteriaModel = craft.retsRabbit.searches.criteria() %}
{% set criteria = criteriaModel
.forId(searchId)
.select('ListPrice', 'StreetNumber', 'StateOrProvince', 'StreetName', 'StreetDirSuffix', 'PublicRemarks', 'BathroomsFull', 'BedroomsTotal', 'ListingId', 'photos')
.orderBy('ListPrice', 'desc')
.limit(24)
.countBy('exact')
%}
{% rrPaginate criteria as pageInfo, viewModel %}
{% if viewModel.hasErrors() %}
<article class="message is-danger">
<div class="message-header">
<p>Uh oh...</p>
</div>
<div class="message-body">
We could not process your request. Please try again.
</div>
</article>
{% elseif not viewModel.hasData() %}
<article class="message is-warning">
<div class="message-header">
<p>Hmm..</p>
</div>
<div class="message-body">
We could not find any results for your search. Try changing your search parameters.
</div>
</article>
{% else %}
{% set listings = viewModel.data %}
<div class="columns is-multiline">
{% for listing in listings %}
<div class="column is-4">
{% include "properties/includes/_grid-item" %}
</div>
{% endfor %}
</div>
{% if pageInfo.totalPages > 1 %}
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
{% if pageInfo.prevUrl %}
<a class="pagination-previous" aria-label="Previous page" href="{{pageInfo.prevUrl}}">Previous</a>
{% endif %}
{% if pageInfo.nextUrl %}
<a class="pagination-next" aria-label="Next page" href="{{pageInfo.nextUrl}}">Next page</a>
{% endif %}
<ul class="pagination-list">
{% if pageInfo.currentPage > 2 %}
<li>
<a href="{{pageInfo.firstUrl}}" class="pagination-link" aria-label="Goto first page">First</a>
</li>
<li>
<span class="pagination-ellipsis">…</span>
</li>
{% endif %}
{% for page, url in pageInfo.getPrevUrls(2) %}
<li>
<a class="pagination-link" aria-label="Goto page {{page}}" href="{{ url }}">{{ page }}</a>
</li>
{% endfor %}
<li>
<a class="pagination-link is-current" aria-label="Page {{pageInfo.currentPage}}" aria-current="page">{{pageInfo.currentPage}}</a>
</li>
{% for page, url in pageInfo.getNextUrls(2) %}
<li>
<a class="pagination-link" href="{{ url }}" aria-label="Goto page {{page}}">{{ page }}</a>
</li>
{% endfor %}
{% if pageInfo.nextUrl %}
<li>
<span class="pagination-ellipsis">…</span>
</li>
<li>
<a href="{{pageInfo.lastUrl}}" class="pagination-link" aria-label="Goto last page">Last</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% endif %}
我们在这个示例中使用了Bulma.io,但上述标记将生成类似以下的内容。
其他变量
除了PropertiesVariable和OpenHousesVariable之外,您在模板中还可以访问几个其他变量。
- SearchesVariable -
craft.retsRabbit.searches
SearchesVariable
此模板变量有以下方法
bool exists(int $id)
此方法检查给定的搜索ID是否存在。此方法在尝试执行搜索之前检查搜索是否存在非常有用,这将提供更可预测的错误处理。