apcdatanalytics/rets-rabbit

以简单直观的方式在你的工艺网站上展示房地产列表。

1.1.0 2019-09-25 12:33 UTC

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交互。

开放房屋

  1. craft.retsRabbit.openHouses.find - 单个开放房屋查找
  2. 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 %}

属性

  1. craft.retsRabbit.properties.find - 单个列表查找
  2. craft.retsRabbit.properties.query - 运行原始RESO查询
  3. 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,这将允许您为用户提供漂亮的表单。

必填字段

您的搜索表单必须有两个输入。

  1. actionInput("rets-rabbit/properties/search)
  2. redirectInput("search/results/{searchId}")

注意:您的redirect输入必须包含{searchId}术语,以便处理表单POST的控制器端点可以将您重定向到包含保存搜索ID的结果页面。

我们相信以下三种搜索类型可以覆盖大多数搜索表单使用案例。

  1. 单个字段用于单个值
  2. 单个字段用于多个值
  3. 多个字段用于单个值
搜索表单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">&hellip;</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">&hellip;</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,但上述标记将生成类似以下的内容。

分页

其他变量

除了PropertiesVariableOpenHousesVariable之外,您在模板中还可以访问几个其他变量。

  • SearchesVariable - craft.retsRabbit.searches

SearchesVariable

此模板变量有以下方法

  1. exists

bool exists(int $id)

此方法检查给定的搜索ID是否存在。此方法在尝试执行搜索之前检查搜索是否存在非常有用,这将提供更可预测的错误处理。