分类 Web Front End 下的文章

问题:

angular 1.4.x 版本开始,<div ng-repeat="(key, value) in myObj"> ... </div> 的方法移除了按照首字母排序的规则,改为使用浏览器默认的方法,也就是for key in myObj

因此当我们使用 angular-filter 的 groupBy 方法将数组分组时,分组后的数组不会再按照 key 排序,某些情况下就会造成一些麻烦。

解决方案:

根据 这一条说明,利用 toArray 和 orderBy 可以解决这一个问题。

toArray 方法可以将对象转换成固定的数组。这个方法接受一个 bool 类型的参数,用来指定是否将对象的 key 作为转换后数组中的元素的一个属性输出,如果设为 true,那么数组元素将会多一个属性 $key 代表原来的对象的 key

所以我们可以这样完成 groupBy 方法的排序:

<ul ng-repeat="group in myObj | groupBy: 'groupKey' | toArray: true | orderBy: '$key'">
  <li ng-bind="$key"></li>
  <li ng-repeat="item in group"></li>
</ul>

这样,groupBy 的结果就会按照对应的参数的首字母(这里是groupKey)排序了。

默认情况下,$resource 服务会对它进行的 http 请求的 response 默认进行一些处理,它会尝试 map 返回值。
如果你的服务器端返回的值符合标准 json 格式,那么不会遇到问题,但是如果你的服务器返回了类似 12 或者 abc 这样子的数据时,你就会发现当你在回调中试图获取 response 时,发生了一些意想不到的事情。
你实际获得的结果可能是这样子的:

{
 "0": "1",
 "1": "2",
 $promise: obj,
 ...
}
{
 "0": "a",
 "1": "b",
 "2": "c",
 $promise: obj,
 ...
}

你得到的值已经是被错误的 map 过了。这当然不是我们想要的,因此,我们需要手动来处理一下。

所以,我们在 $resource 的对应 action 中指定一下 transformResponse 参数。

顾名思义,transformResponse 就是用来指定对 response 的处理方法的,它接受一个方法或者一个由多个方法组成的数组作为值。

function(data, headersGetter)|Array.<function(data, headersGetter)>

因此我们给 $resource 的某个 action 下添加如下一个参数。

transformResponse: function (response) {
 return {data: response};
}

最终的结构是这样子的:

$resource('/api/test', {}, {
  save: {
    method: 'POST',
    transformResponse: function(response) {
      return {
        data: response
      };
    }
  }
});

这样我们就可以在对应的 action (这里是 save 方法) 的回调中取得我们想要的 response 了。
假设你在回调函数中使用 response 作为回调变量名,你可以通过 response.data来获取真实的 response,它不会再被 angular 自动处理了。

一般来说,当我们在页面内部一个可滚动元素上使用滚动时,如果滚动距离超过它的顶部或者底部,就会触发页面滚动,但是在一些情况下,我们不希望引起页面滚动,因为这会影响用户操作。

下面是一种阻止页面滚动的方法。

var scroll = document.getElementsByClassName('scroll')[0];
function handleScroll (e) {
  if (scroll.scrollHeight > scroll.offsetHeight) { // 仅在元素处于可滚动状态下才进行阻止
    if (e.deltaY < 0 && scroll.scrollTop === 0) {
      e.preventDefault();
    }
    if (e.deltaY > 0 && (scroll.scrollTop + scroll.offsetHeight) >= scroll.scrollHeight) {
      e.preventDefault();
    }
  }
}
scroll.addEventListener('mousewheel', handleScroll);

DEMO: JSBIN

  • 360 极速浏览器
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36 QIHU 360EE"
  • 猎豹安全浏览器
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.137 Safari/537.36 LBBROWSER"
  • 搜狗浏览器
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36 SE 2.X MetaSr 1.0"
  • QQ 浏览器
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; QQBrowser/8.0.1374.400)"
  • 百度浏览器
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 BIDUBrowser/6.x Safari/537.36"
  • 360 安全浏览器
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36"
  • 傲游浏览器
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Maxthon/4.4.2.2000 Chrome/30.0.1599.101 Safari/537.36"
  • UC 浏览器PC版
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 UBrowser/3.1.1644.29 Safari/537.36"

要在 Chrome Extension 中使用 Closure library, 需要将 Closure library 按照需求打包。 假设扩展的开发目录如下:

-root
|-css
|-js
|-closure-library
|-menifest.json
|-..
  1. 首先请到 Closure 官方 下载打包工具 compiler.jar
  2. 将 compiler.jar 放到 root 目录下。
  3. 在本目录下任意位置新建 source.js (命名随意),将所有对 Closure library 的依赖请求写入里面。

    goog.require('goog.dom');
    goog.require('goog.events');
    goog.require('goog.asserts');
  4. 运行如下命令

    closure-library/closure/bin/build/closurebuilder.py // 定位到 Closure library 中的 buider.py 脚本
        --root=closure-library/ // 这一项指定 Closure library 的目录位置
        --root=myproject/ // 指定开发项目的位置
        --namespace="myproject.start" // 指定使用的主命名空间
        --output_mode=compiled // output_mode 输出模式使用 cimpiled
        --compiler_jar=compiler.jar // 指定 compiler.jar 的位置
        --compiler_flags="--compilation_level=ADVANCED_OPTIMIZATIONS" // 指定 compiler.jar 的参数,双引号内直接使用 compiler.jar 的参数
        > compiled.lib.js // 指定输出的文件
  5. 直接在 Chrome extension 中引入输出的 compiled.lib.js 就可以正常使用 Closure library 了。builder.py 脚本会自动检测你所依赖的 Closure library 类,并将其打包。你还可以将命令直接写成 Makefile 脚本,下次直接运行脚本即可。

        development:
          closure-library/closure/bin/build/closurebuilder.py --root=closure-library/ --root=js/ --namespace="YourRootNameSpace" --output_mode=compiled --compiler_jar=compiler.jar > compiled.js
        production:
          closure-library/closure/bin/build/closurebuilder.py --root=closure-library/ --root=js/ --namespace="YourRootNameSpace" --output_mode=compiled --compiler_jar=compiler.jar --compiler_flags="--compilation_level=ADVANCED_OPTIMIZATIONS" > compiled.js
        all:
          development

首先看一下 goog.dom.htmlToDocumentFragment 的实现代码:

goog.dom.htmlToDocumentFragment_ = function(doc, htmlString) {
      var tempDiv = doc.createElement('div');
      if (goog.dom.BrowserFeature.INNER_HTML_NEEDS_SCOPED_ELEMENT) {
        tempDiv.innerHTML = '
        ' + htmlString;
        tempDiv.removeChild(tempDiv.firstChild);
      } else {
        tempDiv.innerHTML = htmlString;
      }
      if (tempDiv.childNodes.length == 1) {
        return /** @type {!Node} */ (tempDiv.removeChild(tempDiv.firstChild));
      } else {
        var fragment = doc.createDocumentFragment();
        while (tempDiv.firstChild) {
          fragment.appendChild(tempDiv.firstChild);
        }
        return fragment;
      }
    };

这个方法利用一个临时 Div 容器来生成 Dom 片段,因为<tr>标签本身不能在<div>标签内使用,所以这个函数不能返回单独地<tr>标签。

在使用 Google Closure Library 时,要注意一点。 goog.require() 所请求的内容,不能被同一个 script 标签内的 js 代码所使用,例如下面这种做法就是错误的

<script src="closure-library/closure/goog/base.js"></script>
<script>
// 不要这样写
goog.require('goog.dom');
var newHeader = goog.dom.createDom('h1');
</script>

如果你这样做,会有一个 cannot read property 'xxx' of undefined 类似的错误。除非 goog.dom.createDom() 在此前已经被请求,不然是无法使用它的。 正确的做法是这样:

<script src="closure-library/closure/goog/base.js"></script>
<script>
goog.require('goog.dom');
</script>
<script>
var newHeader = goog.dom.createDom('h1');
</script>

也可以这样

goog.require('goog.dom');
function sayHi() {
 var newHeader = goog.dom.createDom('h1', {'style': 'background-color:#EEE'},
 'Hello world!');
 goog.dom.appendChild(document.body, newHeader);
}

这样的话,只需要在其他 script 标签或者文件中调用 sayHi() 函数即可。 参考 Getting Started with the Closure Library

在写弹出菜单插件时,需要实现点击弹出菜单以外的区域时关闭菜单的功能。 发现了一种比较简单地实现这一事件的方法。

$(document).mouseup(function(e){
 var _con = $(' 目标区域 '); // 设置目标区域
 if(!_con.is(e.target) && _con.has(e.target).length === 0){ // Mark 1
 some code... // 功能代码
 }
});
/* Mark 1 的原理:
判断点击事件发生在区域外的条件是:
1. 点击事件的对象不是目标区域本身
2. 事件对象同时也不是目标区域的子元素
*/

因为自己要用,所以写了一个弹出菜单插件

jQueryPopMenu

simple responsive popup menu, it’s a jQuery plugin.

Demo

http://gucheen.github.io/jQueryPopMenu/

Link

https://github.com/gucheen/jQueryPopMenu

Intro

这是一个基于 jQuery 的弹出菜单插件。有基本的自适应特性。可以设置包括背景色、菜单按钮大小、圆角或者直角等。
:Demo页面中的所有icon都基于FontAwesome,并非必须。可以自行更改。

jQuery.popmenu-1

jQuery.popmenu-2

Info

Version 1.0
基于jQuery 2.0.3 构建。 Chrome, Safari, Firefox, Opera 可用。无IE支持。

Usage

使用 $(' #target ').popmenu(); 调用。 参数包括:

{
 'controller': true, //设定是否使用控制按钮,设置为false,菜单将一直显示
 'width': '300px', //菜单总宽度
 'background': '#34495e', //菜单背景色
 'focusColor': '#1abc9c', //菜单按钮hover时颜色
 'borderRadius': '10px', //边角弧度,设置为0,为直角
 'top': '50', //上移距离,向上移动多少就设置为多少
 'left': '0', //左移距离,设置同上
 'iconSize': '100px' //菜单按钮大小,目前是正方形设计(宽高相同)
}
//以上参数数值为默认值
//使用时无需受Demo影响,按钮可以是纯文字,内容排版自行安排设计。

注:控制按钮对应 .pop_ctrl,即名字为 pop_ctrl 的class,使用class是为了方便同时使用多个菜单,各菜单间无影响。