D7 theme API 概述

对于Drupal的主体来说,如果是比较简单的项目,直接在主题模板tpl的基础上覆写或是配合主题的预处理器基本上就可以实现需求。而对于高度定制化的需求,Drupal也可以很方便的实现需求,这里就不得不说到drupal在主题层面上提供了强大的API供大家使用。

主题渲染的流程

$flow
st=>start: 路由匹配回调执行 hook_menu callback
e=>end: 前端 html
op1=>operation: 可渲染数组 Renderable array
op2=>operation: 预处理加工 preprocessor
op3=>operation: 处理加工 processor
op4=>operation: 模板打印输出 tpl.php
st->op1->op2->op3->op4->e
$

页面html的生成与可渲染数组Renderable Array

对于D7来说,一个页面的整体完全是由一个大数组构建并按需将数组元素转化为html,构成页面的建筑材料以数组作为规范,也可以是一个AJAX响应,或者一个由hook_menu定义的路由回调的反馈结果,这也就意味着你的页面回调应该返回一个数组而不是字符串作为结果,数组的好处是结果可以很方便的增删改,而整串html拼接的字符操作起来灵活性很差。在这里我们统一将构成页面的数组称为可渲染数组(Renderable array).

一个D7页面响应回调的例子

//页面的两部分表格+分页,而实际的项目中,很可能还会有其他的元素,比如页头、页脚、侧边栏等加入构建完整页面所需要的数组中
//这个例子中,module_name_page函数很可能是一个menu路径的响应回调函数
//回调的结果$build就是一个可渲染数组

function module_name_page() {
  $build = array();
  $build['table'] = array(
    //键名含有'#'的表示的是主题的属性值,不带'#'的一般为该主题元素的子元素
    //这里需要注意的是#theme和#theme_wrappers这两个属性,这两个属性建对应的字符串值实际上是生成html的回调函数
    //'#theme'的值'table'负责表格的生成
    //'#theme_wrappers'在'#theme'执行完之后执行,可以在渲染后的html外添加自定义的html,例如在字段的html外添加字段组html的包裹元素如div等,注意'#theme_wrappers'的回调函数的执行结果必须包含元素的'#children'属性,该属性包含'#theme'回调函数生成的html和子元素
    '#theme' => 'table',
    '#header' => $header,
    '#rows' => $rows,
  );
  $build['pager'] = array(
    '#theme' => 'pager',
  );
  return $build;
}

注:关于 ‘#theme’和 ‘#theme_wrappers’,官网API文档drupal_render( )有详细说明

前端对响应回调的处理

针对响应的可渲染数组,Drupal将这些结果交给主题的预处理器(preprocessor)和处理器(processor)进一步加工处理以方便在PHPTemplate的模板文件tpl中使用。

以一个node的页面为例

node页面的内容(renderable array)由函数node_view提供,回调结果提供给template_preprocess_node( )来预处理,预处理的作用是将大数组Renderable Array分解为一个个小数组,而这些小数组可以直接在模板文件node.tpl.php直接打印出来,打印完的结果,就是最终的html页面了。


function node_view($node, $view_mode = 'full', $langcode = NULL) { if (!isset($langcode)) { $langcode = $GLOBALS['language_content']->language; } // Populate $node->content with a render() array. node_build_content($node, $view_mode, $langcode); $build = $node->content; // We don't need duplicate rendering info in node->content. unset($node->content); $build += array( '#theme' => 'node', '#node' => $node, '#view_mode' => $view_mode, '#language' => $langcode, ); // Add contextual links for this node, except when the node is already being // displayed on its own page. Modules may alter this behavior (for example, // to restrict contextual links to certain view modes) by implementing // hook_node_view_alter(). if (!empty($node->nid) && !($view_mode == 'full' && node_is_page($node))) { $build['#contextual_links']['node'] = array('node', array($node->nid)); } // Allow modules to modify the structured node. $type = 'node'; drupal_alter(array('node_view', 'entity_view'), $build, $type); return $build; } // 这里的view_mode、teaser、title、page等将可以在模板文件中被直接打印 function template_preprocess_node(&$variables) { $variables['view_mode'] = $variables['elements']['#view_mode']; // Provide a distinct $teaser boolean. $variables['teaser'] = $variables['view_mode'] == 'teaser'; $variables['node'] = $variables['elements']['#node']; $node = $variables['node']; $variables['date'] = format_date($node->created); $uri = entity_uri('node', $node); $variables['node_url'] = url($uri['path'], $uri['options']); $variables['title'] = check_plain($node->title); $variables['page'] = $variables['view_mode'] == 'full' && node_is_page($node); // Flatten the node object's member fields. $variables = array_merge((array) $node, $variables); // Helpful $content variable for templates. $variables += array('content' => array()); foreach (element_children($variables['elements']) as $key) { $variables['content'][$key] = $variables['elements'][$key]; } ... }

node.tpl.php模板文件

<div id="node-<?php print $node->nid; ?>" class="<?php print $classes; ?> clearfix"<?php print $attributes; ?>>

  <?php print $user_picture; ?>

  <?php print render($title_prefix); ?>
  <?php if (!$page): ?>
    <h2<?php print $title_attributes; ?>><a href="<?php print $node_url; ?>"><?php print $title; ?></a></h2>
  <?php endif; ?>
  <?php print render($title_suffix); ?>

  <?php if ($display_submitted): ?>
    <div class="submitted">
      <?php print $submitted; ?>
    </div>
  <?php endif; ?>

  <div class="content"<?php print $content_attributes; ?>>
    <?php
      // We hide the comments and links now so that we can render them later.
      hide($content['comments']);
      hide($content['links']);
      print render($content);
    ?>
  </div>

  <?php print render($content['links']); ?>

  <?php print render($content['comments']); ?>

</div>

为什么要折腾 Linkedin

回答这个问题,需要先回答什么是Linkedin,Linkedin是面向职场人员的社交平台,也就是说这是一个和工作相关的社交平台,与Facebook晒吃喝晒美照晒萌娃不同的是,这里晒的是职场精英的工作经历和工作能力,晒他们帮助老板挣钱的能力,所以对于B端(B2B)来说,在这里更有可能找到理想的客户和合作伙伴。

如何去拓展linkedin社交圈子

  1. 创建内容
    • 个人职场照片
    • 完善的个人profile
    • 完善的工作经历与公司介绍
    • 公司的Group页面
    • 动态更新公司最近的events和stories

    这些工作的目的,只有一个,告诉你的潜在客户,你是真实的,你的公司是真实的,这样才有信任的一个基础。

  2. 拓展圈子(Connect)

行业大佬
优秀同行

linkedin仅仅针对connections来说,是没有办法通过搜寻相关的关键词来定位你要添加的人的,因此最好的办法莫过于借助以上两种途径。其中如果你添加了同行,也就意味着,在你圈子拓展的过程中,你的资源间接的也可以被你的同行所利用。这里如何拿下客户,就得靠公司实力和自己的真本事了。

  1. 使用工具

我不建议大家每天花费太多的时间在社交媒体上找客户,社交媒体平均的转化率是低于一般的途径的,而原因主要为三点,流量的垄断,人们的行为习惯以及行业的特点。

针对B2B来说,长久以来人们习惯通过搜索引擎,B2B平台来找供应商,这两种方式占据了流量的绝大部分,但是这也并不意味着社交媒体这一块就挖不到客户,或是社交媒体无用论。社媒的运作,需要一个持久的过程,其作用也是厚积薄发的。社交媒体上公司的动态更新与同步,配合视频图文,可以提高公司的真实性和可信度,从侧面的角度提高公司营销在传统途径的转化率,降低平均的询盘成本。

只有量变才能积累质变,如果你的linkedin账户只有300个connection,而你却想从这300个connection里得到一个订单,这其实是不现实的。 而从30000个connection里得到一个订单,其实是很容易的。

工欲善其事必先利其器,Linkedin的操作需要工具的辅助来提高效率。如果每天要我花费半小时的时间来刷connect页面,然后一个一个的去点击按钮connect,在这期间我什么干不了,只能机械的重复动作,一天两天是可以的,时间长了人是会发疯的,就如同以前要求每个业务员每天发布30个产品一样。

# 2017年08月01日09:39:36
# 由于前一段时间Linkedin改版,以上代码已经失效
#
jQuery('.button-secondary-small').each(function(index, value) {
  setTimeout(function() {
    jQuery(value).trigger('click');
  }, index * 1000);
});

Lindedin页面采用的是AJAX载入实现页面无需重载而更新,以上的代码是基于jquery插件Linkedin自动connect代码,美中不足就是无法自动下拉页面,只有手动的去滑动页面才能一次添加更多的人,离实现我们的需求还差的很远。因此,我不得不利用零零碎碎的时间,自己来开发一个小工具来帮助工作提高效率。说实话,虽然我没有学过javascript,但是这门语言似乎没有我想想的那么难。或许代码写的比较烂,我需要的功能基本都实现了。

/*
 * Linkedin 1 click connect tools
 *
 * Version: 1.0-alpha
 * This tools is developed based on Chrome for MAC 60.0
 * Previous versions support will not be considered
 * Use at your own risk
 * /


/* Find the embed element */
mainDiv = document.querySelector(".mn-connections-summary__no-top-border-radius.p0");


/* Add the action form to DOM */

form = document.createElement("form");
form.className = "mn-connections-summary ember-view";
mainDiv.appendChild(form);
form.style.width = "90%";

/*
 * Button START SCROLL will only scroll the page, and button STOP SCROLL will stop scroll.
 * After the scroll process is done, then you can hit the CONNECT All button to connect people on this page.
 * ONE CLICK TO CONNECT will auto scroll down and connect people with your settings or default if none available.
 */

 form.innerHTML = ''
    + '<lable style="font-size: 10px; text-align: left;">How much time to spare for connecting people? - 10 mins default</lable>'
    + '<input style="margin-top:5px; margin-right: 5px;"class= "time-mins-scroll" placeholder="number of minutes" type="text" name="scroll_time">'
    + '<lable style="font-size: 10px; text-align: left;">Page fresh interval - 5s default</lable>'
    + '<input style="margin-top:5px; margin-right: 5px;"class= "scroll-interval" placeholder="number of seconds" type="text" name="scroll_interval">'
    + '<input style="margin-top:5px; margin-right: 5px;"class= "start-scroll-btn button-secondary-medium" type="button" value="Start scroll">'
    + '<input style="margin-top:5px; margin-right: 5px;"class= "stop-scroll-btn button-secondary-medium" type="button" value="Stop scroll">'
    + '<input style="margin-top:5px; margin-right: 5px;"class= "connect-all-btn button-secondary-medium" type="button" value="Connect all">'
    + '<input style="margin-top:5px; margin-right: 5px;"class= "one-click-connect-btn button-secondary-medium" type="button" value="One Click to Connect all">';

var startScrollBtn = document.querySelector(".start-scroll-btn"),
stopScrollBtn = document.querySelector(".stop-scroll-btn"),
connectAllBtn = document.querySelector(".connect-all-btn"),
oneClickConnectBtn = document.querySelector(".one-click-connect-btn");

var cycleTimes = 0, scrollCycle = 10 * 60, scrollInterval = 5, scrollIntevalInit, peoplesOnPage = {};

(document.querySelector(".time-mins-scroll").value == "") ? scrollCycle = 10 * 60 : scrollCycle = document.querySelector(".time-mins-scroll").value * 60;

(document.querySelector(".scroll-interval").value == "") ? scrollInterval = 5 : scrollInterval = document.querySelector(".scroll-interval").value;


scrollToBottom = function(){
    scrollHeight = document.documentElement.scrollHeight;
    window.scrollTo(0, scrollHeight);
    cycleTimes = cycleTimes + 1;
    console.log("page refresh #" + cycleTimes);
};

//@todo 阻止重复点击事件


startScrollBtn.addEventListener("click", function(){

    (document.querySelector(".time-mins-scroll").value == "") ? scrollCycle = 10 * 60 : scrollCycle = document.querySelector(".time-mins-scroll").value * 60;
    (document.querySelector(".scroll-interval").value == "") ? scrollInterval = 5 : scrollInterval = document.querySelector(".scroll-interval").value;
    console.log(scrollCycle);
    console.log(scrollInterval);
    if(cycleTimes <= (scrollCycle / scrollInterval)){
        scrollIntevalInit = setInterval(scrollToBottom, scrollInterval * 1000);
    }
}, false);


//@todo 阻止重复点击事件

stopScrollBtn.addEventListener("click",function(){
    clearInterval(scrollIntevalInit);
});

connectAllBtn.addEventListener("click",function(){

    clearInterval(scrollIntevalInit);

    peoplesOnPage = document.querySelectorAll(".mn-pymk-list__action-container .button-secondary-small");
    for (var i = 0 ; i < peoplesOnPage.length; i++) {
        (function(index){
            setTimeout(
                function(){
                    peoplesOnPage[index].click();
                    console.log("#" + index + "clicked");
                }, scrollInterval * index * 1000);
        })(i);
    }
});

oneClickConnectBtn.addEventListener("click", function () {

    (document.querySelector(".time-mins-scroll").value == "") ? scrollCycle = 10 * 60 : scrollCycle = document.querySelector(".time-mins-scroll").value * 60;
    (document.querySelector(".scroll-interval").value == "") ? scrollInterval = 5 : scrollInterval = document.querySelector(".scroll-interval").value;

    scrollIntevalInit = setInterval(scrollToBottom, scrollInterval * 1000);

    if(cycleTimes > (scrollCycle/scrollInterval)){

        clearInterval(scrollIntevalInit);

        peoplesOnPage = document.querySelectorAll(".mn-pymk-list__action-container .button-secondary-small");

        for (var i = 0 ; i < peoplesOnPage.length; i++) {
            (function(index){
                setTimeout(
                    function(){
                        console.log(index);
                        peoplesOnPage[index].click();
                    }, scrollInterval * index * 1000);
            })(i);
        }
    }
});